C#泛型:有没有一种方法可以将泛型参数类型作为集合引用?

7

我需要编写一堆方法,这些方法接受1到N个通用类型参数,例如:

int Foo<T1>();
int Foo<T1,T2>();
int Foo<T1,T2,T3>();
...
int Foo<T1,T2,T3...TN>();

Foo() 内,我想针对每种类型做一些操作,例如:

int Foo<T1,T2,T3>() {
    this.data = new byte[3]; // allocate 1 array slot per type
}

有没有一种方法可以将这个参数化,以便我不需要编辑每个 Foo() 的变体,类似于以下内容:
int Foo<T1,T2,T3>() {
    this.data = new byte[_NUMBER_OF_GENERIC_PARAMETERS];
}

理想情况下,我还希望能够获取类型的数组或集合:
int Foo<T1,T2,T3>() {
    this.data = new byte[_NUMBER_OF_GENERIC_PARAMETERS];

    // can do this
    Type [] types = new Type[] { T1, T2, T3 };
    // but would rather do this
    Type [] types = _ARRAY_OR_COLLECTION_OF_THE_GENERIC_PARAMETERS;
}

在C#类型系统的上下文中,拥有可变泛型参数列表并没有太多意义 - 如果方法签名中有这些类型,你会如何定义一个方法呢?如果在方法签名中从未使用类型参数,直接使用Type的集合会更合适。(除非你试图捕获可变参数列表的编译时类型,但这也没有太多意义 - params列表是一个预定类型的数组。) - millimoose
看一下这个,https://dev59.com/9nVD5IYBdhLWcg3wO5AD,但我认为它可能带来更多问题而不值得。 - Tony Hopkinson
请查看Jon Skeet在https://dev59.com/AXVC5IYBdhLWcg3wsTVi的回答。根据您的需求,您可能可以使用params关键字。 - Kirk
4个回答

8
您可以从 MethodInfo.GetGenericArguments 数组 中读取当前通用参数及其数量。
您可以使用 MethodBase.GetCurrentMethod 方法 检索当前方法的 MethodInfo
请注意,由于 C# 和 CLI 不支持可变泛型参数列表,因此您仍然需要提供几个不同数量的通用重载方法。
因此,您三个通用参数的方法示例可以编写如下:
int Foo<T1,T2,T3>() {
    MethodInfo mInfo = (MethodInfo)MethodBase.GetCurrentMethod();
    Type[] types = mInfo.GetGenericArguments();

    this.data = new byte[types.Length];
}

谢谢您先生!(我认为您的回复中有一个错别字-应该是GetGenericArguments()而不是GenericTypeArguments()) - CoderBrien
@CoderBrien:你说得对 - GenericTypeArguments 存在于 Type 类中。已更正。 - O. R. Mapper
我知道你想避免在编译时设置“类型”和“数据”,即使用你在编译时拥有的信息,但请注意,这种方法将负担转移到运行时;也就是说,这种使用反射的动态方法会对性能产生影响,而静态方法则不会(因为编译器会完成所有工作)。 - stakx - no longer contributing
1
我理解反射的性能影响。对于我的使用情况,这不是问题。我并不试图避免使用编译时信息。我试图最小化我1..N个复制方法的代码维护。 - CoderBrien

2

1)您可以通过反射获取模板参数的数量:http://msdn.microsoft.com/zh-cn/library/system.reflection.methodbase.getcurrentmethod.aspx。这样,您就可以为每个Foo拥有通用实现。在每个Foo中,您只需调用:

FooImpl();

唯一的不同之处(关于“GetCurrentMethod”)是您需要获取前一个方法的方法信息:
StackTrace stackTrace = new StackTrace();
MethodBase methodBase = stackTrace.GetFrame(1).GetMethod();

2) 您可以在运行时生成所有Foo版本 - 所有版本只共享调用FooImpl的相同实现。有关在运行时生成方法的详细信息,请参见:Creating a function dynamically at run-time 和这里:http://msdn.microsoft.com/en-us/library/exczf7b9.aspx


1
.NET框架认为具有N个类型参数的泛型类或方法与具有更多或更少参数的类或方法名称不同。可能可以在不对框架进行大幅更改的情况下安排事情,以便调用函数。
foo<T>(autogeneric ref T it)

as:

foo(1, "George", 5.7);

会被翻译成:

struct foo99 {public int p1; public string p2; public double p3};
...
foo99 temp99;
temp99.p1 = 1;
temp99.p2 = "George";
temp99.p3 = 5.7;
foo1(ref temp);

那将允许一个通用的方法高效地接受任意数量的参数。传递这样的匿名结构体可能看起来并不是非常有用,但与lambda结合使用时可以非常强大。
通常,在lambda表达式中关闭局部变量需要将局部变量提升到堆对象并构建一个委托,该委托针对该堆对象。如果可以使用上述方式,则可以将适当的变量提升到结构中,并传递一个byref以及一个静态委托,而不是创建一个持久化闭包对象和委托并传递委托。这仅适用于被调用例程不必保留传入的闭包的情况,但反过来,调用者将知道被调用例程不会保留闭包。此外,虽然没有.NET语言支持这样的事情,并且可能需要一些框架更改来允许执行此操作的代码易失性,但通过引用传递结构体,其中一些成员也是byrefs,可以使lambda能够访问封闭过程的ref参数--目前不可能做到这一点(因为不能保证创建的委托不会超出其创建的范围)。结构在它们所在的作用域死亡时死亡,因此问题不必存在于它们身上。
我希望.NET语言有一种方便的方式来表达这些概念。闭包可能会导致变量的生命周期任意持久化,这意味着使用闭包中的变量的代码必须假设外部代码随时可能更改它。基于结构体的方法就不会有这个问题。

0
不行。您不能随意使用Type参数。但是您可以使用类似Tuple的东西。它允许您包装泛型。但是您不能使用TypeParameter本身。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接