我知道我正在做的事情可以用不同的方式完成,但我对事情的运作方式很好奇。以下是一个不编译的简化代码,但它应该显示我的目标。
private void Execute()
{
GeneralizedFunction("1", "2", i => Transform(i));
}
void GeneralizedFunction(string aStringA, string aStringB, Func<string, T> aAction)
{
A result1 = aAction(aStringA);
B result2 = aAction(aStringB);
// Do something with A and B here
}
T Transform<T>(string aString)
{
return default(T);
}
Transform
是从字符串到某个对象的通用转换(想想反序列化)。
GeneralizedFunction
使用两个转换特化:一个用于类型A,一个用于类型B.我知道我可以通过许多其他方式执行此操作(例如通过引入对象类型的参数),但我正在寻找解释是否有可能或不可能使用泛型/ lambdas。如果Transform在作为参数传递给GeneralizedFunction之前是专用的,那么它是不可能的。那么问题是为什么这种可能性受到限制。
您要求做的事情不可能单独使用仿制药。编译器需要生成两个类型版本的 Transform
功能:一个返回类型 A
和一个类型 B
。编译器无法知道在编译时生成它;只有运行代码才能知道A和B是必需的。
解决它的一种方法是传递两个版本:
private void Execute()
{
GeneralizedFunction("1", "2", i => Transform<A>(i), i => Transform<B>(i));
}
void GeneralizedFunction(string aStringA, string aStringB, Func<string, A> aAction, Func<string, B> bAction)
{
A result1 = aAction(aStringA);
B result2 = bAction(aStringB);
}
在这种情况下,编译器确切地知道它需要生成什么。
这个答案没有解释原因 为什么,只是 怎么样 解决限制。
您可以传递具有此类功能的对象,而不是传递实际函数:
interface IGenericFunc
{
TResult Call<TArg,TResult>(TArg arg);
}
// ... in some class:
void Test(IGenericFunc genericFunc)
{
// for example's sake only:
int x = genericFunc.Call<String, int>("string");
object y = genericFunc.Call<double, object>(2.3);
}
对于您的特定用例,可以简化为:
interface IDeserializerFunc
{
T Call<T>(string arg);
}
// ... in some class:
void Test(IDeserializerFunc deserializer)
{
int x = deserializer.Call<int>("3");
double y = deserializer.Call<double>("3.2");
}
请尝试以下签名:
void GeneralizedFunction<T>(string aStringA, string aStringB, Func<string, T> aAction)
(注意,GeneralizedFunction必须是通用的;编译器会在调用方法时自动猜测类型参数)。
似乎答案是“不”。
你打电话时 Transform
直接,你必须指定一个类型参数:
int i = Transform<int>("");
所以假设,如果你可以像你想要的那样传递一个不完整构造的泛型函数,你还需要指定类型参数:
void GeneralizedFunction(string aStringA, string aStringB, Func<string, T> aAction)
{
A result1 = aAction<A>(aStringA);
B result2 = aAction<B>(aStringB);
// Do something with A and B here
}
所以在我看来,如果C#有这样的语法,你可以假设这样做。
但是用例是什么?除了将字符串转换为任意类型的默认值之外,我认为没有太多用处。你怎么能定义一个函数,它可以使用同一系列的语句在两种不同类型中的任何一种中提供有意义的结果?
编辑
分析为什么不可能:
在代码中使用lambda表达式时,它将被编译为委托或表达式树;在这种情况下,它是一个代表。您不能拥有“开放”泛型类型的实例;换句话说,要从泛型类型创建对象,必须指定所有类型参数。换句话说,如果没有为其所有类型参数提供参数,就无法拥有委托实例。
C#编译器的一个有用功能是隐式方法组转换,其中方法的名称(“方法组”)可以隐式转换为表示该方法的一个重载的委托类型。类似地,编译器隐式地将lambda表达式转换为委托类型。在这两种情况下,编译器都会发出代码来创建委托类型的实例(在这种情况下,将其传递给函数)。但是该委托类型的实例仍然需要为每个类型参数都有一个类型参数。
传递泛型函数 作为通用功能看起来,编译器需要能够通过 方法组 要么 lambda表达式 方法 没有转换, 所以 aAction
参数会以某种方式具有“方法组”或“lambda表达式”的类型。然后,隐式转换为委托类型可能发生在呼叫站点 A result1 = aAction<A>(aStringA);
和 B result2 = aAction<B>(aStringB);
。当然,在这一点上,我们很好地融入了反事实和假设的世界。
假设有一个功能,我在午餐时提出的解决方案就是这个 Deserialize<T>
采用包含序列化数据的字符串并返回类型的对象 T
:
void GeneralizedFunction<T>(string aStringA, string aStringB, Func<T, string> stringGetter)
{
A result1 = Deserialize<A>(stringGetter(aStringA));
B result2 = Deserialize<B>(stringGetter(aStringB));
}
void Example(string serializedA, string serializedB, string pathToA, string pathToB, FileInfo a, FileInfo b)
{
GeneralizedFunction(serializedA, serializedB, s => s);
GeneralizedFunction(pathToA, pathToB, File.ReadAllText);
GeneralizedFunction(a, b, fi => File.ReadAllText(fi.FullName));
}
void GeneralizedFunction<T>(string aStringA, string aStringB, Func<string, T> aAction)
{
A result1 = aAction(aStringA);
B result2 = aAction(aStringB);
}
T Transform<T>(string aString)
{
return default(T);
}