-
2020-12-22 20:00:51
这里所谓的“泛型方法的类型推断”,指的是根据已有的方法实参的类型,推断出泛型方法的类型实参。例如一个泛型方法 void Method(T[] args),如果我给出方法实参类型是 int[],那么希望能够推断出 T = int。
这个问题是我在测试上一篇随笔《C# 使用 Binder 类自定义反射》中的类时发现的,当时为了能够让 PowerBinder 支持泛型方法绑定,完成了一些简单的类型推断工作,但是它只能支持直接使用泛型参数 T 作为参数类型,对于 T[],IList 这种复杂一些的情况是不能处理的。
或者举个复杂点的例子,对于下面的泛型方法定义:
void Method(IList a, params T[] args);
再给出参数类型为:
{ typeof(IList), typeof(int[]) }
{ typeof(IList), typeof(int[]) }
{ typeof(IList), typeof(int[][]) }
我希望能够正确的推断出T的类型分别为int、int[]和int[]。
后来参考了《CSharp Language Specification》v5.0 中 7.5.2 类型推断一节,规范中给出了 C# 中进行类型推断的两阶段算法,算法分为两阶段主要是为了支持实参表达式和匿名函数的推断,而我的需求则要简单很多,只要支持普通的参数就可以了。又参考了 7.5.2.13 方法组转换的类型推断一节,最终得到了下面的简化算法。
首先对几个名词进行区分:类型形参、类型实参、方法形参和方法实参。
对于泛型方法定义 void Method(T a),其中的 T 是类型形参,T a 是方法形参。
对于相应的封闭泛型方法的调用 Method(10),其中的 int 就是类型实参,10 就是方法实参。
泛型方法的类型推断,从形式上来定义,就是对给定泛型方法 Tr M(T1 x1, …, T_m x_m),其中 Tr 是返回值,X1, …, Xn 是类型形参,T1, …, T_m 是方法形参,和一个委托类型 D(U1 x1, …, U_m x_m),找到一组类型实参 S1, …, Sn,使表达式 M 与 D 兼容(D 可由M 隐式转换而来)。
该算法首先认为所有 Xi 均未固定(即没有预设值),并从 D 的每个实参类型 Ui 到 M 的对应形参类型 Ti 进行下限推断(前提是 Ti 包含类型形参,即ContainsGenericParameters == true),但是如果 xi 为 ref 或 out 形参,则从 Ui 到 Ti 进行精确推断。如果没有为任何 Xi 找到界限,则类型推断将失败。否则,所有将 Xi 均固定到对应的 Si,它们是类型推断的结果。下面给出详细的推断算法,这里的算法经过了我的修改,与原规范并不完全相同。
一、精确推断
这里的精确推断指的是对于给定的实参类型 U,找到合适的形参类型 V,使得 U == V。
按如下所述从类型 U 到类型 V 进行精确推断:
如果 V 是 Xi 之一,则将 U 添加到 Xi 的精确界限集中。
否则,通过检查是否存在以下任何一种情况来确定集合 V1, …, V_k 和 U1, …, U_k:
V 是数组类型 V1[…],U 是具有相同秩的数组类型 U1[…]。
V 是类型 V1?,U 是类型 U1?。
V 是构造类型 C 并且 U 是构造类型 C。
如果存在以上任意情况,则从每个 Ui 到对应的 Vi 进行精确推断。
否则,类型推断将失败。
二、下限推断
这里的下限推断指的是对于给定的实参类型 U,找到合适的形参类型 V,使得 V.IsImplicitFrom(U)。
按如下所述从类型 U 到类型 V 进行下限推断:
如果 V 是 Xi 之一,则将 U 添加到 Xi 的下限界限集中。
否则,如果 V 为 V1? 类型,而 U 为 U1? 类型,则从 U1 到 V1 进行下限推断。
否则,如果 V 是数组类型 V1[…],U 是具有相同秩的数组类型 U1[…],或者 V 是一个 IEnumerable、ICollection 或 IList,U 是一维数组类型 U1[],如果不知道 U1 是引用类型,则从 U1 到 V1 进行精确推断,否则进行下限推断。
否则,如果 V 是构造类、结构、接口或委托类型 C,并且存在唯一类型 C,使 U 等于、(直接或间接)继承自或者(直接或间接)实现 C(“唯一性”限制表示对于 interface C{} class U: C, C{},不进行从 U 到 C 的推断,因为 U1 可以是 X 或Y。),则从每个 Ui 到对应的 Vi 进行推断,如果不知道 U1 是引用类型,则进行精确推断,否则推断依赖于 C 的第 i 个类型参数:
如果该参数是协变的,则进行下限推断。
如果该参数是逆变的,则进行上限推断。
如果该参数是固定的,则进行精确推断。
否则,类型推断将失败。
三、上限推断
这里的上限推断指的是对于给定的实参类型 U,找到合适的形参类型 V,使得 U.IsImplicitFrom(V)。
按如下所述从类型 U 到类型 V 进行上限推断:
如果 V 是 Xi 之一,则将 U 添加到 Xi 的上限界限集中。
否则,如果 V 为 V1? 类型,而 U 为 U1? 类型,则从 U1 到 V1 进行上限推断。
否则,如果 V 是数组类型 V1[…],U 是具有相同秩的数组类型 U1[…],或者 V 是一维数组类型 V1[],U 是一个 IEnumerable、ICollection 或IList,如果不知道 U1 是引用类型,则从 U1 到 V1 进行精确推断,否则进行上限推断。
否则,如果 U 是构造类、结构、接口或委托类型 C,V 是等于、(直接或间接)继承自或者(直接或间接)实现唯一类型 C的类、结构、接口或委托类型(“唯一性”限制表示如果我们有 interface C{} class V: C>, C>{},则不进行从 C 到 V
的推断。也不进行从 U1 到 X
或 Y
的推断。),则从每个 Ui 到对应的 Vi 进行推断,如果不知道 U1 是引用类型,则进行精确推断,否则推断依赖于 C 的第 i 个类型参数:
如果该参数是协变的,则进行上限推断。
如果该参数是逆变的,则进行下限推断。
如果该参数是固定的,则进行精确推断。
否则,类型推断将失败。
四、固定
固定是为了根据之前的算法得到的界限集,推断出类型参数的合适的值。
具有界限集的类型变量 Xi 按如下方式固定:
候选类型集 Ui 是在 Xi 的界限集中的所有类型的集合。
然后我们依次检查 Xi 的每个界限:对于 Xi 的每个精确界限 U,将与 U 不同的所有类型 Ui 都从候选集中移除(要求 U == Ui)。对于 Xi 的每个下限U,将不存在从 U 进行的隐式转换的所有类型 Ui 都从候选集中移除(要求 Ui.IsImplicitFrom(U))。对于 Xi 的每个上限 U,将不存在从其到 U 进行的隐式转换的所有类型 Ui 都从候选集中移除(要求 U.IsImplicitFrom(Ui))。
如果在剩下的候选类型 Ui 中,存在唯一类型 V,该类型可由其他所有候选类型经隐式转换而来,则将 Xi 固定到 V(也就是说,要求 V 是其中最通用的类型)。
否则,类型推断将失败。
以上就是泛型方法的类型推断算法,其中只考虑了方法实参和方法形参一一对应的情况,如果需要处理 params T[] 参数,则需要对最后一个参数进行特殊处理,并分别使用 T 和 T[] 进行一次类型推断。做两次类型推断,就是为了判断是否是方法的展开形式的调用。
或者说,对于泛型方法定义
void Method(T a, params T[] args);
如果参数为{ typeof(int), typeof(int[]) }和{ typeof(int[]), typeof(int[]) },虽然T[]对应的实参是相同的,但推断出的T却是不同的,这就需要利用两次类型推断来处理。
这个算法的实现加上注释大概有 500 多行,这里就不再贴出,基本就是按照上面的 4 步来的,只是在一些细节上采用了更高效的做法。所有源码可以见这里。
更多相关内容 -
C#泛型方法
2020-02-01 17:02:20一、泛型类型的格式 声明: public void Swap(ref T x,ref T y) //函数名后要跟表示类型用占位符T代替 { //函数体 } 使用: int x=10,y=20; Swap(ref x,ref y); //自动识别,然后用实际类型替换T 示例: ...一、泛型方法的格式
声明:
public void Swap<T>(ref T x,ref T y) //函数名后要跟表示类型用占位符T代替
{
//函数体
}使用:
int x=10,y=20;
Swap(ref x,ref y); //自动识别,然后用实际类型替换T示例:
class Program { public static void Swap<T>(ref T x, ref T y) { T temp = x; x = y; y = temp; } static void Main(string[] args) { int x = 10, y = 55; Console.WriteLine("{0},{1}",x,y); //输出10,55 Swap(ref x,ref y);//完整的为Swap<int>(ref x,ref y);<int>可以省 Console.WriteLine("{0},{1}",x,y); //输出55,10 string q = "张三",p = "李四"; Console.WriteLine(q+p); //输出张三李四 Swap(ref q, ref p); Console.WriteLine(q+p); //输出李四张三 Console.ReadKey(); } }
二、泛型的使用范围
只有类型和方法可以引入类型参数T,属性、索引器、时间、字段、构造函数、操作符等都不可以声明类型参数。但是他们可以使用他们所在的泛型类型中的类型参数。
例如:
public Stack<T>(){}; //错误,不能用在构造函数上public T Pop() //正确,可以使用他们所在泛型类中的类型参数T
{ //方法体};可以使用的范围有class、struct、interface、delegate。
如:
public struct Nullable<T> //正确,可以用在struct上
{
public T value{get;} //正确,可以使用所示类型的类型参数T
}三、多个参数的泛型方法使用
泛型类型或方法中可以存在多种泛型参数。如下:`
class Program { public static void Fuc<Tname,Tscore>(Tname i,Tscore j) { Console.WriteLine("{0}的分数为{1}",i,j); } static void Main(string[] args) { string Nam = "王二"; int Sco = 100; //Fuc(Sco,Nam); //可以省略<string,int>,编译器会按照参数顺序推测,但是这种不健壮 Fuc<string, int>(Nam,Sco);//建议加上<类型说明>,提高代码健壮性,Nam与Sco不能颠倒否则会报错 Console.ReadKey(); } }
-
C#泛型方法在lua中表示的一种设计详解
2020-08-26 06:43:27主要给大家介绍了关于C#泛型方法在lua中表示的一种设计的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧 -
C#泛型类、泛型方法、泛型接口、泛型委托的实例
2018-07-26 15:21:14泛型类、泛型方法、泛型接口、泛型委托 泛型类、泛型方法、泛型接口、泛型委托 -
C#泛型方法的定义及使用
2019-10-17 11:58:43在C#语言中泛型方法是指通过泛型来约束方法中的参数类型,也可以理解为对数据类型设置了参数。 如果没有泛型,每次方法中的参数类型都是固定的,不能随意更改。 在使用泛型后,方法中的数据类型则有指定的泛型来...在 C# 语言中泛型方法是指通过泛型来约束方法中的参数类型,也可以理解为对数据类型设置了参数。
如果没有泛型,每次方法中的参数类型都是固定的,不能随意更改。
在使用泛型后,方法中的数据类型则有指定的泛型来约束,即可以根据提供的泛型来传递不同类型的参数。
定义泛型方法需要在方法名和参数列表之间加上<>
,并在其中使用T
来代表参数类型。
当然,也可以使用其他的标识符来代替参数类型, 但通常都使用T
来表示。下面通过实例来演示泛型方法的使用。【实例】创建泛型方法,实现对两个数的求和运算。
根据题目要求,代码如下。
class Program { static void Main(string[] args) { //将T设置为double类型 Add<double>(3.3, 4); //将T设置为int类型 Add<int>(3, 4); } //加法运算 private static void Add<T>(T a, T b) { double sum = double.Parse(a.ToString()) + double.Parse(b.ToString()); Console.WriteLine(sum); } }
执行上面的代码,效果如下图所示。
从上面的执行效果可以看出,在调用 Add 方法时能指定不同的参数类型执行加法运算。
如果在调用 Add 方法时,没有按照 <T> 中规定的类型传递参数,则会出现编译错误,这样就可以尽量避免程序在运行时出现异常。
-
C#泛型及方法
2022-06-26 14:06:57C#泛型及方法泛型: 允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换句话说,泛型允许您编写一个可以与任何数据类型一起工作的类或方法。
您可以通过数据类型的替代参数编写类或方法的规范。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理指定的数据类型。下面这个简单的实例将有助于您理解这个概念:
实例:
namespace 博客 { public class MyGenericArray<T> { private T[] array; public MyGenericArray(int size) { array = new T[size + 1]; } public T getItem(int index) { return array[index]; } public void setItem(int index, T value) { array[index] = value; } } internal class Program { static void Main(string[] args) { // 声明一个整型数组 MyGenericArray<int> intArray = new MyGenericArray<int>(5); // 设置值 for (int c = 0; c < 5; c++) { intArray.setItem(c, c * 5); } // 获取值 for (int c = 0; c < 5; c++) { Console.Write(intArray.getItem(c) + " "); } Console.WriteLine(); // 声明一个字符数组 MyGenericArray<char> charArray = new MyGenericArray<char>(5); // 设置值 for (int c = 0; c < 5; c++) { charArray.setItem(c, (char)(c + 97)); } // 获取值 for (int c = 0; c < 5; c++) { Console.Write(charArray.getItem(c) + " "); } Console.WriteLine(); Console.ReadKey(); } } }
当上面的代码被编译和执行时,它会产生下列结果:
泛型(Generic)的特性
使用泛型是一种增强程序功能的技术,具体表现在以下几个方面:
- 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。
- 您可以创建泛型集合类。.NET 框架类库在 System.Collections.Generic 命名空间中包含了一些新的泛型集合类。您可以使用这些泛型集合类来替代 System.Collections 中的集合类。
- 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
- 您可以对泛型类进行约束以访问特定数据类型的方法。
- 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。
泛型 方法
在上面的实例中,我们已经使用了泛型类,我们可以通过类型参数声明泛型方法。下面的程序说明了这个概念:
实例:
namespace 博客 { internal class Program { static void Swap<T>(ref T lhs, ref T rhs) { T temp; temp = lhs; lhs = rhs; rhs = temp; } static void Main(string[] args) { int a, b; char c, d; a = 10; b = 20; c = 'I'; d = 'V'; // 在交换之前显示值 Console.WriteLine("Int values before calling swap:"); Console.WriteLine("a = {0}, b = {1}", a, b); Console.WriteLine("Char values before calling swap:"); Console.WriteLine("c = {0}, d = {1}", c, d); // 调用 swap Swap<int>(ref a, ref b); Swap<char>(ref c, ref d); // 在交换之后显示值 Console.WriteLine("Int values after calling swap:"); Console.WriteLine("a = {0}, b = {1}", a, b); Console.WriteLine("Char values after calling swap:"); Console.WriteLine("c = {0}, d = {1}", c, d); Console.ReadKey(); } } }
当上面的代码被编译和执行时,它会产生下列结果:
-
C#泛型方法和普通方法的性能实例解析
2018-09-11 12:04:44也因此泛型方法在泛型类中不仅仅具备强大的重用性,而且还具备更强大的性能。 强类型的元素”更早识别类型”。 而这也是称之为强类型的原因,因为在编译时没有办法告诉我们列表中数据的实际类型是什么, 泛型通过... -
C# 泛型方法
2020-05-13 10:32:42除了定义泛型类之外,还可以定义泛型方法。 在泛型方法中,泛型类型用方法声明来定义。泛型方法可以在非泛型类中定义。 // Swap<T>() 方法把 T 定义为泛型类型。 void Swap<T> (ref T x, ref T y) {... -
c#泛型类和泛型方法使用实例
2020-10-07 16:43:42泛型类: using System; namespace ThreadDemo { class Program { static void Main(string[] args) { MyStack<int> myStack = new MyStack<int>(3); myStack.Push(1); myStack.Push(2); -
C#泛型类和泛型方法
2019-10-21 20:16:46泛型可以理解为广泛的类型,或者不确定的类型,也就是说允许我们编写一个可以与任何数据类型同时运行,且不报错的方法或者类。 泛型类 泛型类是指这个类的某些字段的类型是不确定的,只有在构造的时候才能确定下的类... -
C#泛型方法、传统方法与object传值性能的比较
2020-08-08 00:28:41一、测试方法 分别对几种方法进行一亿次的循环,每次循环调用一次相应的方法。... //泛型方法 static void N2<T>(T a) { } //使用object传值方法 static void N3(object a) { } 二、测试主代码 p -
C#的泛型方法解析
2020-12-26 10:24:35C#2.0引入了泛型这个特性,由于泛型的引入,在一定程度上极大的增强了C#的生命力,可以完成C#1.0时需要编写复杂代码才可以完成的一些功能。但是作为开发者,对于泛型可谓是又爱又恨,爱的是其强大的功能,以及该特性... -
C# 泛型方法Where 约束
2018-01-19 10:24:58where(泛型类型约束)定义:在定义泛型的时候,我们可以使用 where 限制参数的范围。使用:在使用泛型的时候,你必须尊守 where 限制参数的范围,否则编译不会通过。 六种类型的约束:T:类(类型参数必须是... -
C# 泛型类型、泛型方法
2020-06-19 00:16:36泛型类型 Generic types 泛型会声明类型参数—泛型的消费者需要提供类型参数来把占位符类型填充 public class Stack<T> { int positon; T[] data=new T[100]; public void Push(T obj)=>data[position++... -
C#泛型约束的深入理解
2020-12-25 19:09:40where 子句用于指定类型约束,这些约束可以作为泛型声明中定义的类型参数的变量。1.接口约束。例如,可以声明一个泛型类 MyGenericClass,这样,类型参数 T 就可以实现 IComparable<T> 接口: 代码如下:public class... -
C#的泛型方法
2021-09-08 08:43:24在C#语言中泛型方法是指通过泛型来约束方法中的参数类型,也可以理解为对数据类型设 置了参数。 如果没有泛型,每次方法中的参数类型都是固定的,不能随意更改。 在使用泛型后,方法中的数据类型则有指定的... -
c#泛型类、泛型方法、泛型接口、泛型委托
2021-01-08 16:58:57c#泛型类、泛型方法、泛型接口、泛型委托 -
C# 泛型约束
2022-05-29 20:44:211、泛型约束 让泛型的类型有一定的限制 关键字:where 泛型约束一共有6种: 值类型 where 泛型字母:struct 引用类型 where 泛型字母:class 存在无参公共构造函数 where 泛型字母:new() 某个类... -
C#泛型类与泛型方法总结
2020-06-11 11:21:11前言:本博文从C#泛型入手,依次介绍泛型类和泛型方法,如果读者想对泛型有更多的了解,可访问本人另一篇博文:C#中List泛型用法,必知必会! 文章目录一、泛型是什么?二、泛型类的定义三、泛型方法定义 一、泛型是... -
Unity XLua(九)Lua调用C#泛型参数方法+调用泛型方法的方法+泛型方法
2020-06-12 15:56:05C# using System.Collections; using System.Collections.Generic; using UnityEngine; using XLua; using System.IO; using System.Text; using UnityEngine.UI; public class TestXLua : MonoBehaviour { ... -
C#泛型约束
2022-05-24 20:08:10泛型的六种约束 -
详解C#泛型的类型参数约束
2020-08-18 17:18:49主要介绍了C#泛型的类型参数约束的相关资料,文中讲解非常细致,帮助大家更好的理解和学习c#,感兴趣的朋友可以了解下 -
详解C# 泛型中的数据类型判定与转换
2020-12-17 09:05:33提到类型转换,首先要明确C#中的数据类型,主要分为值类型和引用类型: 1.常用的值类型有:(struct) 整型家族:int,byte,char,short,long等等一系列 浮点家族:float,double,decimal 孤独的枚举:enum 孤独... -
C#泛型方法注意事项
2020-09-10 11:07:43泛型方法中<>的值可以定义成已存在的类型,但是有歧义,虽然说后面方法中定义的是DataTable,但是实际存储的是调用时传入的类型(字符串) -
C#泛型和泛型约束
2021-08-17 21:36:58一、泛型: 所谓泛型,即通过参数化类型来实现在同一份代码上操作多种数据类型。泛型编程是一种编程范式,它利用“参数化类型”将类型抽象化,从而实现更为灵活的复用。 二、泛型约束: 在定义泛型类时,可以... -
C# 泛型声明
2020-11-27 11:45:11怎么声明 //泛型类 public class GenericClass<T> where T : ISports { } //泛型接口 public interface Generic...//泛型方法 public void test<T>(T param) { } 比如我声明一个泛型类 public class T -
C#泛型案例与教程
2021-01-04 01:33:03泛型接口–教程案例 using System; namespace 泛型接口 { class Program { static void Main(string[] args) { //实例化接口 IGenericInterface<System.ComponentModel.IListSource> factory = new ... -
C#泛型-泛型方法
2019-01-08 01:41:27除了可以定义泛型类之外还可以定义泛型方法 把方法Swap把T定义为泛型类型,两个参数,一个变量temp public static void Swap<T>(ref T x, ref T y) { T temp; temp = x; x = y; ...