问题描述
我想将返回泛型类型参数的特定实现的方法分配给值 Func<T>
。 T 被约束为非托管。 Func 将用于填充列表。
本计划是使用检查 (typeof(T) == typeof(int)
找到类型 T,但代码无法编译。
public static List<T> CreateSimulationList<T>(int amount,Func<T> simulator = null)
where T : unmanaged
{
if (simulator == null)
{
//this sends an error: "has the wrong return type"
if (typeof(T) == typeof(int)) { simulator = FillWithZeroInt; }
//this sends an error: "has the wrong return type"
if (typeof(T) == typeof(float)) { simulator = FillWithOneFloat; }
}
List<T> list = new List<T>(amount);
for (int i = 0; i < amount; i++)
{
list.Add(simulator());
}
return list;
}
private static int FillWithZeroInt() => 0;
private static float FillWithOneFloat() => 1f;
有趣的是,Rider 并未在 IDE 内报告该问题。但是 Unity 编译器可以:
'MyImplementationStruct SimulateA' has the wrong return type.
'MyImplementationStructB SimulateB' has the wrong return type.
我从the comments那里得到了原因(感谢@canton7)
问题当然是typeof检查是运行时检查,但是 编译器需要在编译时断言
如果我声明泛型类型并直接传递函数,它就可以工作。
//this code works
var generatedList = CreateSimulationList<int>(10,FillWithZeroInt);
我想在编译时推断 T 的类型并分配一个默认方法,或者找到任何其他方法来避免在方法参数中设置 Func<T>
,并且没有装箱。
感谢您阅读本文。
解决方法
首先,这有点设计味道 - 在泛型方法中使用特定类型的匹配通常并不理想。我承认偶尔这是必需的。
您可以通过强制转换来使其工作...您需要强制转换为具体的 Func
类型以开始(或使用另一种形式的委托初始化),然后转换为 object
基本上阻止编译器认为它知道什么是有效的,然后转换为 Func<T>
。这是一个完整的示例:
using System;
class Program
{
static void Main()
{
var func1 = CreateFunc<int>();
var func2 = CreateFunc<string>();
Console.WriteLine(func1);
Console.WriteLine(func2);
}
static Func<T> CreateFunc<T>()
{
if (typeof(T) == typeof(int))
{
return (Func<T>) (object) (Func<int>) Int32Func;
}
if (typeof(T) == typeof(string))
{
return (Func<T>) (object) (Func<string>) StringFunc;
}
return null;
}
static int Int32Func() => 0;
static string StringFunc() => "";
}
另一种替代方法是使用 lambda 表达式并将方法调用的结果转换为 T
。同样,您需要在转换为 object
之前转换为 T
:
if (typeof(T) == typeof(int))
{
return () => (T) (object) Int32Func();
}
if (typeof(T) == typeof(string))
{
return () => (T) (object) StringFunc();
}
请注意,如果 T
是值类型, 每次调用都会涉及装箱,而委托本身的转换则不会。
Jon 已经发布了我要发布的一半内容,因此我不会重复他的工作。但是,我确实想补充一点建议,因为我 100% 同意他关于“设计气味”和“不理想”的说明(恕我直言),我建议您应该将您的实现分解为特定于类型的方法。
换句话说,与其让方法通用,不如让两个方法拥有你想要处理的签名:
public static NativeList<MyImplementationStruct> CreateSimulationListA(int amount,Allocator allocator,Func<MyImplementationStruct> simulator = null)
{
simulator = simulator ?? SimulateA;
NativeList<T> list = new NativeList<T>(amount,allocator);
for (int i = 0; i < amount; i++)
{
list.Add(simulator());
}
return list;
}
public static NativeList<MyImplementationStructB> CreateSimulationListB(int amount,Func<MyImplementationStructB> simulator = null)
{
simulator = simulator ?? SimulateB;
NativeList<T> list = new NativeList<T>(amount,allocator);
for (int i = 0; i < amount; i++)
{
list.Add(simulator());
}
return list;
}
您甚至可以通过保留泛型版本但不允许该参数为可选或 null 来减少重复代码,并仍然支持涉及其他类型的场景:
public static NativeList<MyImplementationStruct> CreateSimulationListA(int amount,Func<MyImplementationStruct> simulator = null)
{
return CreateSimulationList(amount,allocator,simulator ?? SimulateA);
}
public static NativeList<MyImplementationStructB> CreateSimulationListB(int amount,Func<MyImplementationStructB> simulator = null)
{
return CreateSimulationList(amount,simulator ?? SimulateB);
}
public static NativeList<T> CreateSimulationList<T>(int amount,Func<T> simulator)
where T : unmanaged,IMyInterface
{
NativeList<T> list = new NativeList<T>(amount,allocator);
for (int i = 0; i < amount; i++)
{
list.Add(simulator());
}
return list;
}
由于泛型类型推断在缺少参数的情况下无论如何都不起作用,因此您必须使用显式类型参数调用该方法,因此自定义调用站点以通过其各自的名称调用非泛型方法应该'不辛苦。
以上反映了这样一个事实,即代码一开始似乎并不真正是通用的,因此尝试将方法变为通用是错误的。最好将类型硬编码为非泛型方法,而将泛型部分留给方法可以真正泛型的场景。