标签:gate blog 函数 组类型 泛型方法 generic names object target
允许延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候
在泛型类型的定义中,出现的每个T(一个展位变量而已叫别的名字也行)在运行时都会被替换成实际的类型参数。
现在有一个需求,需要写一个方法,这个方法传入的参数可能是int型的,也可能是string型的。首先我们可以用方法的重载方案解决这个问题,比如下面两个重载方法:
public void test(int param)
{ }
public void test(string param)
{ }
但是这样的话如果支持的类型变多了,那么你需要写很多重载方法。如果只写一个重载方法,则可以写成
public void test(object param) { }
但是这样写又出现object转成其他类型的问题,会带来效率损失。同时不检查类型,一旦传入了不支持的类型,可能会出问题。
现在把test方法改造一下,这样写:
public void test<T>(T param){ }
这样写之后,使用的时候要求提前通知这个方法,你传入的是什么类型,即:
test<int>(10);
如果写成 test<int>("10");编译器就会报错。
这就是泛型方法。这里面我们省略了方法内部的实现,其实仔细想一下,如果要在这样的方法里面添加业务代码,似乎除了用于存放数据的集合之外,并没有多少场景需要这么写方法。没错,泛型这个东西最常用的应用场景就是数据集合。而List<T>就是一个存放各种数据的泛型类。
上面的方法:public void test<T>(T param){ },我们可以尝试一下把<T>去掉,只写成public void test(T param){ }看看会发生什么。你会发现编译器会报错,那么我们再尝试一下在这个方法的类名上加上<T>,即写成:
class TClass<T>
{
public void test(T param)
{ }
}
你会发现,如果把<T>放到类名上,里面的方法就不需要加 <T>了,同时编译器也不会报错。这是一种比较简洁的写法。这个时候,TClass这个类就是泛型类,而它的构造方法,则和普通的类的构造方法的写法是一样的。当你要实例化这个类型的时候,必须告诉这个类型T代表哪个类型,之后,所有这个类里面被标识了T的地方,都是指你开始实例化指明的类型。比如test这个方法里面传入的param,一定要和你开始实例化这个类的时候指明的类型一致。再比如你写一个返回T的方法: public T returnTest() { },这个方法的返回值也必须是你实例化类时指明的类型。如果我们TClass改成List,把test改成Add,则方法变成了下面这样
class List<T>
{
public void Add(T param)
{ }
}
这不就是我们经常用的List<T>这个泛型集合吗。当然它的内部实现还有很多东西,这里我们不去关注。
参考链接:https://www.cnblogs.com/ypa-yap-yap/p/11523736.html
为什么要泛型约束,其主要问题还是解决安全问题,规范开发人员写代码的规范性,避免一些在运行时期才能检查到的错误。比如下面的代码,在编译器是不会报错的,但是在运行期会出现转换异常。
所谓的泛型约束,实际上就是约束的类型T。使T必须遵循一定的规则。比如T必须继承自某个类,或者T必须实现某个接口等等。那么怎么给泛型指定约束?其实也很简单,只需要where关键字,加上约束的条件。
序号 | 约束 | 说明 |
---|---|---|
1 | T:struct | 类型参数必须是值类型 |
2 | T:class | 类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。 |
3 | T:new() | 类型参数必须具有无参数的公共构造函数。 当与其他约束一起使用时,new() 约束必须最后指定。 |
4 | T:基类名 | 类型参数必须是指定的基类或派生自指定的基类。 |
5 | T:接口名称 | 类型参数必须是指定的接口或实现指定的接口。 可以指定多个接口约束。 约束接口也可以是泛型的。 |
6 | T:基类名,接口名称,new() | 泛型约束也可以同时约束多个,但是new()必须放在最后 |
在OO的世界里,可以安全地把子类的引用赋给父类引用。但是在T的世界里,就不一定了。有的能变,有的不能变,先了解以下几点:
如果不能理解以上几句话,就先看下面的知识点
所谓协变,就是为了解决子类泛型接口或委托能回到父类泛型接口或委托上来。(Foo<父类> = Foo<子类> )
//泛型委托:
public delegate T MyFuncA<T>();//不支持逆变与协变
public delegate T MyFuncB<out T>();//支持协变
MyFuncA<object> funcAObject = null;
MyFuncA<string> funcAString = null;
MyFuncB<object> funcBObject = null;
MyFuncB<string> funcBString = null;
MyFuncB<int> funcBInt = null;
funcAObject = funcAString;//编译失败,MyFuncA不支持逆变与协变
funcBObject = funcBString;//变了,协变
funcBObject = funcBInt;//编译失败,值类型不参与协变或逆变
//泛型接口
public interface IFlyA<T> { }//不支持逆变与协变
public interface IFlyB<out T> { }//支持协变
IFlyA<object> flyAObject = null;
IFlyA<string> flyAString = null;
IFlyB<object> flyBObject = null;
IFlyB<string> flyBString = null;
IFlyB<int> flyBInt = null;
flyAObject = flyAString;//编译失败,IFlyA不支持逆变与协变
flyBObject = flyBString;//变了,协变
flyBObject = flyBInt;//编译失败,值类型不参与协变或逆变
//数组:
string[] strings = new string[] { "string" };
object[] objects = strings;
所谓协变,就是为了解决父类泛型接口或委托能回到子类泛型接口或委托上来。(Foo<子类> = Foo<父类>)
public delegate void MyActionA<T>(T param);//不支持逆变与协变
public delegate void MyActionB<in T>(T param);//支持逆变
public interface IPlayA<T> { }//不支持逆变与协变
public interface IPlayB<in T> { }//支持逆变
MyActionA<object> actionAObject = null;
MyActionA<string> actionAString = null;
MyActionB<object> actionBObject = null;
MyActionB<string> actionBString = null;
actionAString = actionAObject;//MyActionA不支持逆变与协变,编译失败
actionBString = actionBObject;//变了,逆变
IPlayA<object> playAObject = null;
IPlayA<string> playAString = null;
IPlayB<object> playBObject = null;
IPlayB<string> playBString = null;
playAString = playAObject;//IPlayA不支持逆变与协变,编译失败
playBString = playBObject;//变了,逆变
注意
in/out是什么意思呢?为什么加了它们就有了“变”的能力,是不是我们定义泛型委托或者接口都应该添加它们呢?
原来,在泛型参数上添加了in关键字作为泛型修饰符的话,那么那个泛型参数就只能用作方法的输入参数,或者只写属性的参数,不能作为方法返回值等,总之就是只能是“入”,不能出。out关键字反之。
泛型在声明的时候可以不指定具体的类型,但是在使用的时候必须指定具体类型;
如果子类也是泛型的,那么继承的时候可以不指定具体类型
namespace MyGeneric
{
/// <summary>
/// 使用泛型的时候必须指定具体类型,
/// 这里的具体类型是int
/// </summary>
public class CommonClass :GenericClass<int>
{
}
/// <summary>
/// 子类也是泛型的,继承的时候可以不指定具体类型
/// </summary>
/// <typeparam name="T"></typeparam>
public class CommonClassChild<T>:GenericClass<T>
{
}
}
类实现泛型接口也是这种情况
namespace MyGeneric
{
/// <summary>
/// 必须指定具体类型
/// </summary>
public class Common : IGenericInterface<string>
{
public string GetT(string t)
{
throw new NotImplementedException();
}
}
/// <summary>
/// 可以不知道具体类型,但是子类也必须是泛型的
/// </summary>
/// <typeparam name="T"></typeparam>
public class CommonChild<T> : IGenericInterface<T>
{
public T GetT(T t)
{
throw new NotImplementedException();
}
}
}
参考链接:https://www.cnblogs.com/zhan520g/p/10397117.html#idx_10
标签:gate blog 函数 组类型 泛型方法 generic names object target
原文地址:https://www.cnblogs.com/zhaoyl9/p/12174500.html