- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章C#基础之泛型由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
1.泛型的本质 。
泛型的好处不用多说,在.NET中我看到有很多技术都是以泛型为基础的,不过因为不懂泛型而只能对那些技术一脸茫然。泛型主要用于集合类,最主要的原因是它不需要装箱拆箱且类型安全,比如很常用的List<T>。对于List<T>我以后还想进行深究,现在我写了一个超简版的MyList<T>集合,如下面第一段代码所示。代码很简单,但在写的过程中有一个细节,如果我为listInt赋值string类型的变量时编译器会提示报错。编译器很智能,但是从这个现象中你会不会好奇泛型中的T是在什么情况下指定的呢,是生成IL时还是JIT动态编译时?老方法我将exe放入Reflector工具中,发现IL代码中全是T,这说明在编译时T仅仅只是一个占位符,真真的替换是在运行时动态替换的。可是在写泛型类时代码只有一份,那我为MyList创建int、string类型的对象时这个代码是如何公用的呢?对于值类型集合比如listInt,由于最终需要替换T,那么肯定是有一份完整的代码里面T被替换为int。对于引用类型,因为变量只是一个指向堆中的指针,因此代码只有一份。总结起来就是值类型代码有多份而引用类型代码只有一份,另外编写自定义泛型代码时最好使用有意义的T,比如.net中常见的TResult表示返回值,这样可读性较好.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
class
Program
{
static
void
Main(
string
[] args)
{
MyList<
int
> listInt =
new
MyList<
int
>();
MyList<
string
> listString =
new
MyList<
string
>();
listInt.Add(24);
listInt[1] = 5;
listString[2] =
"ha ha"
;
}
}
public
class
MyList<T>
{
T[] array;
int
current = -1;
public
MyList()
{
array =
new
T[10];
}
public
void
Add(T t)
{
current++;
if
(current < 10)
array[current] = t;
}
public
T
this
[
int
index]
{
get
{
return
array[index];
}
set
{
array[index] = value;
}
}
}
|
2.泛型规范 。
这个很重要,主要包括约束和default。.NET是推荐我们开发者尽可能的多使用约束,因为约束越多越可以保证程序不会出错。泛型约束由where指定,六种约束如下所示。这些约束可以单独使用也可以一起使用,但也有不能一起使用的比如值类型与引用类型约束。关于default的作用我们可以思考这样一个问题,如果在泛型类中我们需要初始化一个T变量。因为T既有可能是值类型也有可能是引用类型,所以不能直接用new或等于0。那如何判断T是值类型还是引用类型呢?这里就要用到default,对于引用类型default(T)将返回null,对于数值类型default(T)将返回0。这里之所以写数值类型是因为值类型还可能是结构体,default会将结构体中的成员初始化为0或null。还有一种特殊情况就是可空值类型,此时将返回Nullable<T>,这样初始变量直接使用T t=default(T)就可以了。虽然泛型类给人带来了神秘感,不过运行时它的本质就是一个普通的类,因此依旧具有类的特性比如继承。这为我们开发者带来了很多好处,比如我想要有一个int集合类,它除了有List<int>的功能外还有自定义的某些功能,这时候只需MyList : List<int>就可以得到想要的效果了,非常方便.
where T : struct 值类型约束,T必须为值类型.
where T:class 引用类型约束,T必须为引用类型.
where T:new() 构造器约束,T必须拥有公共无参构造函数且new()约束放在最后.
where T:U 裸类型约束,T必须是U或派生自U.
where T:BaseClass 基类约束,T必须为BaseClass类或其子类.
where T:Interface 接口约束,T必须为指定的接口或其实现接口.
3.反射创建泛型 。
和非泛型类一样,利用反射可以在运行时获取关于泛型类的成员信息。在学习过程我没想到竟然还可以使用反射创建泛型类,更神奇的是还可以在代码里直接写IL指令,代码如下所示。流程上还是那个规则,创建程序集-模块-类-字段和方法,其中最主要的就是Builder结尾的一系列方法。有一个不好理解的地方就是为方法添加方法体,正常的逻辑是直接调用ReturnType的有参构造函数创建List<TName1>对象,可是在.NET里并没有这样的方法,注意这里ReturnType已经是绑定了TName1的List对象而不是普通的List<T>。所以我们需要拿到List<T>这个类型的有参构造函数,它被封装在cInfo对象里,然后再将我们的ReturnType和cInfo关联起来得到List<TName1>的构造函数。除了构造函数中的T需要替换为TName1外,参数IEnumerable<T>中的T也要被替换为TName1,体现在代码里是这一句ienumOf.MakeGenericType(TFromListOf),最后它将随构造函数一起与TName1进行关联。在创建Hello方法我将它设置为静态的,原本我是想设置为实例方法然后调试时去看看是否真的添加了这个方法,不过很奇怪我创建的实例o作为Invoke的参数总是报错提示无法找到方法入口,监视o发现里面根本没有Hello方法,在静态方法下调试也没有从o里看到有关方法的信息,如果读者你对此有自己的想法欢迎留言,如有错误还请指出.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
public
class
BaseClass { }
public
interface
IInterfaceA { }
public
interface
IInterfaceB { }
//作为TName1的类型参数
public
class
ClassT1 { }
//作为TName2的类型参数
public
class
ClassT2 :BaseClass,IInterfaceA, IInterfaceB { }
public
class
ReflectionT
{
public
void
CreateGeneric()
{
//创建一个名为”ReflectionT“的动态程序集,这个程序集可以执行和保存。
AppDomain myDomain = AppDomain.CurrentDomain;
AssemblyName assemblyName =
new
AssemblyName(
"ReflectionT"
);
AssemblyBuilder assemblyBuilder = myDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
//在这个程序集中创建一个与程序集名相同的模块,接着创建一个类MyClass。
ModuleBuilder moudleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, assemblyName.Name +
".dll"
);
TypeBuilder myType = moudleBuilder.DefineType(
"MyClass"
, TypeAttributes.Public);
//创建类型参数名,将达到这样的效果:public MyClass<TParam1,TParam2>
string
[] tNames = {
"TName1"
,
"TName2"
};
GenericTypeParameterBuilder[] gtps = myType.DefineGenericParameters(tNames);
GenericTypeParameterBuilder tName1 = gtps[0];
GenericTypeParameterBuilder tName2 = gtps[1];
//为泛型添加约束,TName1将会被添加构造器约束和引用类型约束
tName1.SetGenericParameterAttributes(GenericParameterAttributes.DefaultConstructorConstraint | GenericParameterAttributes.ReferenceTypeConstraint);
//TName2达到的效果将是:where TName2:ValueType,IComparable,IEnumerable
Type baseType =
typeof
(BaseClass);
Type interfaceA =
typeof
(IInterfaceA);
Type interfaceB =
typeof
(IInterfaceA);
Type[] interfaceTypes = { interfaceA, interfaceB };
tName2.SetBaseTypeConstraint(baseType);
tName2.SetInterfaceConstraints(interfaceTypes);
/*为泛型类MyClass添加字段:
private string name;
public TName1 tField1;
*/
FieldBuilder fieldBuilder = myType.DefineField(
"name"
,
typeof
(
string
), FieldAttributes.Public);
FieldBuilder fieldBuilder2 = myType.DefineField(
"tField1"
, tName1, FieldAttributes.Public);
//为泛型类添加方法Hello
Type listType =
typeof
(List<>);
Type ReturnType = listType.MakeGenericType(tName1);
Type[] parameter = { tName1.MakeArrayType() };
MethodBuilder methodBuilder = myType.DefineMethod(
"Hello"
,
//方法名
MethodAttributes.Public | MethodAttributes.Static,
//指定方法的属性
ReturnType,
//方法的放回类型
parameter);
//方法的参数
//为方法添加方法体
Type ienumOf =
typeof
(IEnumerable<>);
Type TFromListOf = listType.GetGenericArguments()[0];
Type ienumOfT = ienumOf.MakeGenericType(TFromListOf);
Type[] ctorArgs = { ienumOfT };
ConstructorInfo cInfo = listType.GetConstructor(ctorArgs);
//最终的目的是要调用List<TName1>的构造函数 : new List<TName1>(IEnumerable<TName1>);
ConstructorInfo ctor = TypeBuilder.GetConstructor(ReturnType, cInfo);
//设置IL指令
ILGenerator msil = methodBuilder.GetILGenerator();
msil.Emit(OpCodes.Ldarg_0);
msil.Emit(OpCodes.Newobj, ctor);
msil.Emit(OpCodes.Ret);
//创建并保存程序集
Type finished = myType.CreateType();
assemblyBuilder.Save(assemblyName.Name +
".dll"
);
//创建这个MyClass这个类
Type[] typeArgs = {
typeof
(ClassT1),
typeof
(ClassT2) };
Type constructed = finished.MakeGenericType(typeArgs);
object
o = Activator.CreateInstance(constructed);
MethodInfo mi = constructed.GetMethod(
"Hello"
);
ClassT1[] inputParameter = {
new
ClassT1(),
new
ClassT1() };
object
[] arguments = { inputParameter };
List<ClassT1> listResult = (List<ClassT1>)mi.Invoke(
null
, arguments);
//查看返回结果中的数量和完全限定名
Console.WriteLine(listResult.Count);
Console.WriteLine(listResult[0].GetType().FullName);
//查看类型参数以及约束
foreach
(Type t
in
finished.GetGenericArguments())
{
Console.WriteLine(t.ToString());
foreach
(Type c
in
t.GetGenericParameterConstraints())
{
Console.WriteLine(
" "
+c.ToString());
}
}
}
}
|
4.泛型中的out和in 。
在VS查看IEnumerable<T>的定义时会看到在T前面有一个out,与其对应的还有一个in。这就是.NET中的协变与逆变,刚开始笔者对于这2个概念很晕,主要以下4个疑惑,我想如果你解决了的话应该也会有更进一步的认识.
1.为什么需要协变和逆变,协变与逆变有什么效果?
2.为什么有了协变与逆变就可以类型安全的进行转换,不加out和in就不可以转换?
3.使用协变和逆变需要注意什么?
4.协变与逆变为什么只能用于接口和委托?
下面第一段代码解决了第一个问题。对于第二个问题请看第二段代码,里面对无out、in的泛型为什么不安全讲得很清楚,从中我们要注意到如果要当进行协变时Function2是完全ok的,当进行逆变时Function1又是完全ok的。所以加out只是让开发者在代码里无法使用in的功能,加in则是让开发者无法使用out的功能。读者可以自己动手试试,在out T的情况下作为输入参数将会报错,同样将in T作为返回参数也会报错,且VS报错时会直接告诉你这样只能在协变或逆变情况下使用。也就是说加了out后,只有Function2能够编译通过,这样o=str将不会受Function1的影响而不安全;加了in后,只有Function1能够编译通过,这样str=o将不会受Function2的影响而不安全。使用out和in要注意它们只能用于接口和委托,且不能作用于值类型。out用于属性时只能用于只读属性,in则是只写属性,进行协变和逆变时这2个类型参数必须要有继承关系,现在为什么不能用于值类型你应该懂了吧。对于第四个疑惑我没有找到一个完全正确的答案,只是发现了我认同的想法。接口和委托,有什么共同点?显然就是方法,在接口或委托中声明的T都将用于方法且只能用于方法,由上面的讨论可知协变和逆变这种情况正是适用于方法这样的成员。对于在抽象类中不可以使用的原因,或许微软是觉得在抽象类中再搞一个仅限于方法的限制太麻烦了吧.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
interface
INone<T> { }
public
interface
IOut<
out
T> { }
public
interface
IIn<
in
T> { }
public
class
MyClass<T> : INone<T>, IOut<T>, IIn<T> { }
void
hh()
{
INone<
object
> oBase1 =
new
MyClass<
object
>();
INone<
string
> o1 =
new
MyClass<
string
>();
//下面两句都无法编译通过
//o1 = oBase1;
//oBase1 = o1;
//为了能够进行转换,于是出现了协变与逆变
IOut<
object
> oBase2 =
new
MyClass<
object
>();
IOut<
string
> o2 =
new
MyClass<
string
>();
//o2 = oBase2; 编译不通过
//有了out关键字的话,就可以实现从IOut<string>到IOut<object>的转换-往父类转换
oBase2 = o2;
IIn<
object
> oBase3 =
new
MyClass<
object
>();
IIn<
string
> o3 =
new
MyClass<
string
>();
//oBase3 = o3; 编译不通过
//有了in关键字的话,就可以实现从IIn<object>到IOut<string>的转换-往子类转换
o3 = oBase3;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public
interface
INone<T>
{
void
Function1(T tParam);
T Function2();
}
class
MyClass<T> : INone<T>
{
public
void
Function1(T tParam)
{
Console.WriteLine(tParam.ToString());
}
public
T Function2()
{
T t =
default
(T);
return
t;
}
}
class
hhh
{
void
fangyz()
{
INone<
object
> o =
new
MyClass<
object
>();
INone<
string
> str =
new
MyClass<
string
>();
//假设str能够转换为o
//o = str;
object
o1=
new
object
();
//这样的话就是object类型向string类型转换了,类型不安全
o.Function1(o1);
//这样则是string类型向object类型转换了,注意这样是ok的,没什么问题
object
o2=o.Function2();
//假设str能够转换为o
//str=o;
//string对象将转变为object,这样没问题
str.Function1(
"haha"
);
//这样将是object向string类型的转换,类型不安全。
string
o3=str.Function2();
}
}
|
以上所述是小编给大家介绍的C#基础之泛型,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我网站的支持! 。
原文链接:http://www.cnblogs.com/fangyz/p/5737339.html 。
最后此篇关于C#基础之泛型的文章就讲到这里了,如果你想了解更多关于C#基础之泛型的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
vue3 快速入门系列 - 基础 前面我们已经用 vue2 和 react 做过开发了。 从 vue2 升级到 vue3 成本较大,特别是较大的项目。所以许多公司对旧项目继续使用vue2,新项目则
C# 基础 C#项目创建 这里注意win10虚拟机需要更新下补丁,不然直接下载visual studio 2022会显示版本不支持 HelloWorld C#的类文件都是以.cs结尾,入口方法为sta
关于 iPhone 内存管理的非常基本的问题: 假设我有一个 viewController,其中有几个 subview 也由 viewController 控制。当我删除顶部 viewControll
我仍在努力适应指针。不是概念——我理解内存位置、匹配可变长度的指针增量等——这是语法。这是一个我认为是我感到困惑/无法直观把握的原因之一: int a = 42; 在一个int大小的内存空间中分配并放
1. 简介 Kafka(Apache Kafka) 是一种分布式流数据平台,最初由LinkedIn开发,并于后来捐赠给Apache软件基金会,成为了一个Apache顶级项目。它被设计用于处理大规
1.想要在命令提示符下操作mysql服务器,添加系统变量。(计算机-系统属性——环境变量——path) 2.查询数据表中的数据; select selection_lis
MySQL表的增删改查(基础) 1. CRUD 注释:在SQL中可以使用“–空格+描述”来表示注释说明 CRUD 即增加(Create)、查询(Retrieve)、更新(Update)、删除(Dele
我有一个网页,可以在加载时打开显示模式,在这个模式中,我有一个可以打开第二个模式的链接。当第二个模式关闭时(通过单击关闭按钮或单击模式外部),我想重新打开第一个模式。 对于关闭按钮,我可以通过向具有
使用 Core Data Fetched Properties,我如何执行这个简单的请求: 我希望获取的属性 ( myFetchProp ) 存储 StoreA ,它应该这样做: [myFetchPr
关闭。这个问题是opinion-based .它目前不接受答案。 想改进这个问题?更新问题,以便 editing this post 可以用事实和引用来回答它. 8年前关闭。 Improve this
最近,我得到了一个现有的Drupal项目,并被要求改进前端(HTML,JavaScript,CSS)。我在Django,PHP,Ruby等方面具有大量的前端和后端开发经验,但是我没有任何Drupal经
我试图让我的用户通过使用扫描仪类来决定要做什么,但我有一个问题,代码一旦运行就不会激活,并且它不会让我跳过任何行。我的代码如下所示: Scanner input = new Scanner(S
对模糊的标题表示歉意,因为我想不出这个名字是什么。 基本上创建一个计算学生财务付款的小程序。当我运行它时,它计算对象限额没有问题。然而,无论我尝试什么,对象“助学金”似乎除了 0 之外什么也没有提出。
这是我的代码 - main() { double x; double y = pow(((1/3 + sin(x/2))(pow(x, 3) + 3)), 1/3); prin
如果我的术语在这个问题上有误,我们深表歉意。 采取以下功能: i = 1; v = i * 2; for (j = 0; j < 4; j++ ) { console.log(v);
我的应用程序中有不同的类文件。我有 5 个类,其中 2 个是 Activity ,1 个是运行的服务。其他 2 个只是类。这两个类中变量的生命周期是多少。我知道一个 Activity 可以被操作系统杀
例如,一个方法返回一个 List 类型的对象。 public List bojangles () ... 一些代码调用方法FooBar.bojangles.iterator(); 我是 Java 的新
我遇到了一个奇怪的问题,网格的大小不适合我的屏幕。当我使用 12 列大时,它只占据屏幕的 1/3 的中间,请参见图像。我不确定是什么导致了这个问题。我没有任何会导致这种情况发生的奇怪 CSS。我不会在
我尝试使用头文件和源文件,但遇到了问题。因此,我对我正在尝试做的事情做了一个简化版本,我在 CodeBlocks 中遇到了同样的错误(undefined reference to add(double
我正在为我的网格系统使用基础,但这在任何网格系统中都可能是一个问题。我基本上用一个容器包裹了 3 个单元格,但其中一个单元格应该长到页面边框(留在我的 Sampe-Image 中)但这也可能在右侧)。
我是一名优秀的程序员,十分优秀!