4

重学c#系列—— 反射深入一点点[三十三] - 敖毛毛

 1 year ago
source link: https://www.cnblogs.com/aoximin/p/17001399.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

重学c#系列—— 反射深入一点点[三十三]

在上一章中介绍了什么是反射:

https://www.cnblogs.com/aoximin/p/16440966.html

上一节讲述反射的基本原理和为什么要用反射,还用反射的优缺点这些。

1289794-20221223102706601-1387319285.png

其二者的本质是一致的,都是先获取到type(元数据)然后在进行创建实例。

下面那个好理解看下上面那个吧。

1289794-20221223102836933-304151338.png

其实还是调用了activator:

1289794-20221223103050637-868651046.png

说另外一个故事,是先有对象,后执行构造方法。还是先执行构造方法后有对象呢?到底是编译行为还是运行行为呢?

其实先创建对象,然后再进行构造函数。

1289794-20221223105246131-973363685.png

而一切初始化其实是在构造函数中。

public class Cat
{
	private string a = "100";
	public string b = "100";
	
	public Cat()
	{
		a = "200";
		b = "200";
	}
}

那么其.ctor () 为:

1289794-20221223105638169-1088728321.png

计算机远比我们想象的要简单的多,就是开辟一块空间,然后往里面填充数据。 至于定义什么类型,那属于程序的自我管理。

有点扯远了,那么反射的实现也是一样的。

在CreateInstance中:

1289794-20221223110413615-695938617.png

两者创建对象的原理基本是一致的,反射只是在上面增加了一层动态获取类型(其中包括校验和创建实例的代码生成)。

玩一个有趣的东西,既然我们说了,其实创建对象其实不一定要构造函数的。且我们上面知道了RuntimeTypeHandle 可以创建对象,那就直接搞事情。

static void Main(string[] args)
{
	Type catType = typeof(Cat);
	Type handleType = Type.GetType("System.RuntimeTypeHandle");
	var  obj = Activator.CreateInstance(handleType);
	var bind =  BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
	var  hiMethod = handleType.GetMethod("Allocate", bind);
	var   cat= hiMethod.Invoke(obj, new  object[]{catType});
	
	Console.ReadKey();
}

得到的结果为:

1289794-20221223131714971-1689077465.png

好吧,接着往下看:

internal class Program
{
	static void Main(string[] args)
	{
		var type1 = typeof(Cat);
		Cat cat = new Cat();
		var  type2 =cat.GetType();
		
		Assembly assembly = Assembly.GetExecutingAssembly();
		var type3 = assembly.GetType("ConsoleApp1.Cat");
		
		var type4 = typeof(Cat);

		Console.WriteLine($"{type1.GetHashCode()} {type1.GetHashCode()} {type3.GetHashCode()} {type4.GetHashCode()}");
		Console.ReadKey();
	}
	
	static (string name, int age, uint height) GetStudentInfo1()
	{
		return ("Bob", 28, 175);
	}
}

他们的type也是同一个type:

internal class Program
{
	static void Main(string[] args)
	{
		var type1 = typeof(Cat);
		Cat cat = new Cat();
		var  type2 =cat.GetType();
		
		Assembly assembly = Assembly.GetExecutingAssembly();
		var type3 = assembly.GetType("ConsoleApp1.Cat");
		
		var type4 = typeof(Cat);

		Console.WriteLine($"{type1.GetHashCode()} {type2.GetHashCode()} {type3.GetHashCode()} {type4.GetHashCode()}");
		Console.ReadKey();
	}
	
	static (string name, int age, uint height) GetStudentInfo1()
	{
		return ("Bob", 28, 175);
	}
}

1289794-20221223112645563-1475049595.png

值得注意的是typeof 属于语法糖:

1289794-20221223114019871-1265382824.png

现在知道了Type 就包含我们类的元数据了,那么这些元数据到底有哪些呢?

1289794-20221223115041320-2098630588.png

里面包含了描述类的全部信息,有命名空间啊,属性啊,方法啊。这些都是有的。

这些不用去记,用的时候找找看,都有的。

唯一说一个值得的注意的地方哈。

是这样的。有一个BindingFlags这个枚举,可以看到是是一个多选枚举。

1289794-20221223115627824-1135112952.png

然后这样写:

static void Main(string[] args)
{
	var type1 = typeof(Cat);
	var filter = BindingFlags.Public;
	var members = type1.GetMembers(filter);
	
	Console.ReadKey();
}
1289794-20221223115656926-913552876.png

你发现看不到,这是为什么呢?

static void Main(string[] args)
{
	var type1 = typeof(Cat);
	var members = type1.GetMembers();
	
	Console.ReadKey();
}

来看下是什么样的。

1289794-20221223115841616-306772986.png

可以看到,这个public string b,其实是public | instance 这样的bindingflag,而不是public。

也就是说默认的是公共且可实例化的。 这个bindingflag的处理,不是或的关系,而是且的关系。

这里也是大家使用多选枚举值得注意的地方,我们的业务上不仅可以用来做或也可以用来做且,它是多选的意思。

如果需要看下反射方法是怎么调用的,可以去查看:System.RuntimeMethodHandle的InvokeMethod,这里面水比较深,选择性观看。

知道这个有什么用呢? 因为在执行invoke的时候会经过很多判断,如果是为了增加性能,可以直接调用System.RuntimeMethodHandle的InvokeMethod,一般不需要,只是说下优化手段。

反射的常用手段,主要是一些例子。

获取某个namespace 下面的type:

static void Main(string[] args)
{
Assembly assembly = Assembly.GetExecutingAssembly();
var types = assembly.GetTypes().Where(u => u.Namespace == "ConsoleApp1");

Console.ReadKey();
1289794-20221223190411428-97251467.png

这个比较常用,一般来说会先加载出types,得到一个集合,然后进行管理。

有一个初学者容易犯的错误,就是认为:

1289794-20221223191356791-298916673.png

认为这种是属性,其实这个英文是property,是特性的意思,这在有些中文书上直接说成属性,这是错误的。

1289794-20221223191452307-1634914812.png

这种是attribute。

1289794-20221223191747957-1288785951.png

上面这种是字段,叫做field。

然后全部的这些,叫做member,都是成员:

1289794-20221223191828361-263973670.png

这些概念是要区分开的。

第三个小栗子,如果获取静态的值:

static void Main(string[] args)
{
	Type type = typeof(Cat);
	var binding = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
	var  a= type.GetProperties(binding);

	Cat.Test = "123456";
	foreach (var item in a)
	{
		var  test= item.GetValue(null);
		Console.WriteLine($"test:{test}");
	}
	
	Console.ReadKey();
}
1289794-20221223192150643-2059520191.png

上面要说明的是GetValue,对于静态可以填空。

我们讲述到前面的构造函数,都是默认的构造函数。

static void Main(string[] args)
{
	Type type = typeof(Cat);
	var cat = Activator.CreateInstance(type);
	
	Console.ReadKey();
}

这在一般情况下,很难适应,一般我们的构造函数在写一些复杂一点的时候,都会传入参数的。

public Cat(string a, string b)
{
	this.a = a;
	this.b = b;
}

这样的。那么怎么实例化呢?

static void Main(string[] args)
{
	Type type = typeof(Cat);
	var constructorInfo = type.GetConstructor(new  Type[]{ typeof(string), typeof(string) });
	var  cat =constructorInfo.Invoke(new object[] { "123", "456"});
	
	Console.ReadKey();
}

1289794-20221223193435689-1558956977.png

其他的方法也是这样。

上一节讲述了反射,这一节讲了一下反射的大致的行为和一些简单的例子。下一节可能是io流相关的,以前写过Java的,其实两者都一样,考虑是否重新整合一下。或者直接开篇异步篇。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK