问题 C#Generic Generics(一个严肃的问题)


在C#中,我正在尝试编写代码,我将创建一个Func委托,它本身就是通用的。例如,以下(非通用)委托返回任意字符串:

Func<string> getString = () => "Hello!";

另一方面,我希望创建一个类似于泛型方法的泛型。例如,如果我想要一个通用的Func为类型T返回默认值(T)。我想我会按如下方式编写代码:

Func<T><T> getDefaultObject = <T>() => default(T);

然后我会用它作为

getDefaultObject<string>() 哪个会返回null,如果我要写的话 getDefaultObject<int>() 会返回0。

这个问题不仅仅是一个学术上的运动。我找到了许多我可以使用它的地方,但我无法正确使用语法。这可能吗?有没有提供这种功能的库?


10414
2018-05-24 14:22


起源

有什么理由吗? Func<T> CreateGetDefaultObject<T>() { return () => default(T); } 不行吗? - Stephen Cleary
我现在明白了。您希望将编译时类型参数传递给委托。 - Stephen Cleary


答案:


虽然有人会发现 实用的解决方法 像Stephen Cleary的

Func<T> CreateGetDefaultObject<T>() { return () => default(T); }

你可以直接指定泛型,从C#当前类型系统无法解决的理论角度来看,这是一个非常有趣的问题。


一种你称之为的类型 本身就是通用的,被称为 更高级别的类型

请考虑以下示例(pseudo-C#):

Tuple<int[], string[]> Test(Func<?> f) {
    return (f(1), f("Hello"));
} 

在您建议的系统中,呼叫可能如下所示:

Test(x => new[] { x }); // Returns ({ 1 }, { "Hello" })

但问题是:我们如何输入功能 Test 这是争论 f? 显然, f 映射每种类型 T 到一个数组 T[] 这种类型。也许吧?

Tuple<int[], string[]> Test<T>(Func<T, T[]> f) {
    return (f(1), f("Hello"));
} 

但这不起作用。我们无法参数化 Test 同 任何特别的  T从那以后 f 应该适用于 所有 类型 T。此时,C#的类型系统无法进一步发展。

我们需要的是一个符号

Tuple<int[], string[]> Test(forall T : Func<T, T[]> f) {
    return (f(1), f("Hello"));
} 

在您的情况下,您可以键入

forall T : Func<T> getDefaultValue = ...

我知道支持这种泛型的唯一语言是Haskell:

test :: (forall t . t -> [t]) -> ([Int], [String])
test f = (f 1, f "hello")

请参阅此Haskellwiki条目 多态性 对这个 forall 符号。


4
2018-05-24 14:48



感谢达里奥,非常有趣的回答。虽然我不是任何一个Haskell大师,但我理解你的回答和你给出的链接。据我所知,没有解决方案/解决方法,因为这是语言的限制,因为它不支持秩N类型。也许在另外5到10年的时间里,微软会向CLR添加“Rank n Types”。 - Tahir Hassan
@tahirhassan:是的,它是语言类型系统的一个非常基本的属性(而且非常先进的 - 甚至Haskell只支持非标准扩展),这是人们无法超越的。我看到一种可能性,你可以通过实现类似的接口来排序模拟这种行为 interface ToRankNArray { T[] Apply<T>(T arg); }。 Java或F#等语言允许创建匿名子类型,但当然它既不像匿名函数那样方便,也不会改变类型系统本身。 - Dario


那么你不能只根据返回值重载任何东西,所以这包括变量。

但是你可以摆脱lambda表达式并编写一个真正的函数:

T getDefaultObject<T>() { return default(T); }

然后你完全按照自己的意愿调用它:

int i=getDefaultObject<int>();       // i=0
string s=getDefaultObject<string>(); // s=null

8
2018-05-24 14:33



感谢Blindy的回答,但问题更多的是关于动态设置通用通用代理(如果可能的话)。您的代码的问题在于您正在创建一个方法而不是一个可以在方法中声明,实例化和使用的委托。 - Tahir Hassan
没错,但没有回答这个问题。 - Dario


这是不可能的,因为代表  在C#中不能有通用参数。最接近的是将类型对象作为常规参数传递并使用反射。 :(

在很多情况下,铸造到 动态 有助于消除反射的痛苦,但是 动态 在创建新实例时没有帮助,例如您的示例。


0
2018-05-24 14:37





您不能这样做,因为必须在运行时知道泛型类型参数。你必须使用激活类:

Object o = Activator.CreateInstance(typeof(StringBuilder));

这将完全符合你的要求。您可以按以下方式编写它:

public T Default<T>()
{
  return (T)Activator.CreateInstance(typeof(T));
}

编辑

Blindy的解决方案更好。


-1
2018-05-24 14:32