C#语法——反射,架构师的入门基础。
前言
编程其实就是写代码,而写代码目的就是实现业务,所以,语法和框架也是为了实现业务而存在的。因此,不管多么高大上的目标,实质上都是业务。
所以,我认为不要把写代码上升到科学的高度。上升到艺术就可以了,因为艺术本身也没有高度。。。。
软件设计存在过度设计,语法和框架的理解,也存在过度理解。比如,反编译下,看看反射是怎么实现的。。。
有兴趣是好事,但就算知道了反射的本质,了解了反射是如何设计的,你技术也没什么质的改变。因为,技术水平最终还是要落实到应用上。
在比如,过度的追求代码性能,也不见得是一件好事,因为,[大多数]情况下,硬件比程序员便宜多了。。。(注意这里指的是代码不是算法和数据库性能)
所以,不论什么事,过度了,总不是好事。
----------------------------------------------------------------------------------------------------
本篇文章主要介绍c#反射【用法】。
反射是架构师必会的基础,因为任何一个被设计出来的框架,都要使用反射。
反射也是最隐蔽的语法,因为反射写出来后,通常它会被直接封装,然后调用者就只负责使用,不再关注他的具体实现。
这与它的特性有关,因为反射就是为了减少代码冗余而存在的,所以,看不见很正常。
反射的定义
官方定义:反射提供了封装程序集、模块和类型的对象(type 类型)。可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。如果代码中使用了属性,可以利用反射对它们进行访问。
看不懂?没关系,我们把它翻译成人类可理解的语言。
c#编程语言中,最常使用的是类和类中的函数和属性。正向调用的方法是,创建类,然后用类创建一个对象。接下来就可以用这个对象调用类中的方法和属性了。
而反射,就是相对于这种正向调用的存在。即,它是反向调用。
反射可以通过类名的字符串来创建类,可以通过函数名的字符串和属性名的字符串,来调用类下的函数和属性。
有同学会问了, 既然正向可以调用,那么反向调用干什么呢?
会有这种问题的同学,先别着急,继续往下看,反射既然存在,就必然有存在的道理。
反射的基础应用
1,类反射
先看下面代码;代码为通过类名称的字符,反射出类的对象。
public class reflectionsyntax
{
public static void excute()
{
type type = gettype("syntax.kiba");
kiba kiba = (kiba)activator.createinstance(type);
type type2 = gettype2("syntax.kiba");
kiba kiba2 = (kiba)activator.createinstance(type2);
}
public static type gettype(string fullname)
{
assembly assembly = assembly.load("syntax");
type type = assembly.gettype(fullname, true, false);
return type;
}
public static type gettype2(string fullname)
{
type t = type.gettype(fullname);
return t;
}
}
public class kiba
{
public void printname()
{
console.writeline("kiba518");
}
}
在代码中我们看到,反射时传递了字符串"syntax.kiba",然后通过解析字符串,获取到了该字符串对应的类的类型,最后再借助activator来辅助创建类的实例。
其中字符串"syntax.kiba"是一个完全限定名。什么是完全限定名?完全限定名就是命名空间+类名。在反射的时候,需要我们传递完全限定名来确定到底要去哪个命名空间,找哪个类。
在代码中我们还可以看到,获取类型的方式有两种,一种是较复杂的,一种是简单的。
gettype2方法是简单的获取类别,通过type直接就解析了字符串。而gettype则先进行了加载assembly(组件),然后再由组件获取类型。
两者有什么区别呢?
区别是,用type直接解析,只能解析当前命名空间下的类。如果该类存在于引用的dll中,就解析不了。
而gettype方法中的[assembly.load指定了程序集名],所以,在反射时,就会去指定的命名空间里找对应的类。这样就能找到非本程序集下的类了。
[assembly.load指定了程序集名]这句话不好理解?
没关系,换个表达,assembly.load指定了命名空间的名称,所以反射时,会去这个命名空间里找类,这样是不是就好理解了。
assembly
assembly的存在让反射变得特别灵活,其中assembly.load不止可以导入我们引入的程序集(或命名空间)。
也可以导入我们未引入程序集的dll。调用模式如下:
system.reflection.assembly o = system.reflection.assembly.load("mscorlib.dll");
assembly导入了程序集后,还可以不借助activator来辅助,自己就可以创建类。如下:
assembly assembly = assembly.load("syntax");
kiba kiba = (kiba)assembly.createinstance("syntax.kiba");
有的同学可能会担心性能,会觉得这样反射,会使程序变慢。
有这种想法的同学,其实你已经是在过度理解语法了。这种地方的代码性能其实是可以不用关心的。
那么,到底会不会变慢呢?
答案是这样的,如果你是使用完全限定名来反射,速度就是一样的。如果是反射时,只写了一个类名,那么速度就会变慢。因为它要遍历所有的命名空间,去找这个类。
即,只要反射时把类的命名空间写全,那么速度就不会慢。
2,函数反射
函数的反射应用主要是使用类methodinfo类反射,下面先看下基础应用。
public static void excutemethod()
{
assembly assembly = assembly.load("syntax");
type type = assembly.gettype("syntax.kiba", true, false);
methodinfo method = type.getmethod("printname");
object kiba = assembly.createinstance("syntax.kiba");
object[] pmts = new object[] { "kiba518" };
method.invoke(kiba, pmts);//执行方法
}
public class kiba
{
public string name { get; set; }
public void printname(string name)
{
console.writeline(name);
}
}
一些同学第一眼看上去可能会有点不适应,因为好像很多类都是大家不经常用的。这也没办法,因为这是一个进阶的过程,必须经历从陌生到熟悉。当你熟悉了这样的代码后,就代表你的技术水平又进步了一个台阶。
下面讲解一些这些代码。
首先我们导入了命名空间,接着我们获取了该命名空间下kiba这个类的类型;接下来我们通过这个类型来获取指定名称的函数。
然后我们通过assembly创建了一个kiba的实例,接着定义了一个参数的object数组,因为kiba类下的函数printname只有一个参数,所以,我们只为这个object数组添加一个对象[kiba518]。
最后,我们通过method.invoke来调用这个函数,由于是反射,所以调用时,需要指定kiba类的实例对象和入参。
这样,函数的反射就实现了。
3,属性反射
属性反射是用propertyinfo类来实现,下面看基础的属性反射。
public static void excuteproperty()
{
kiba kiba = new kiba();
kiba.name = "kiba518";
object name = reflectionsyntax.getpropertyvalue(kiba, "name");
console.writeline(name);
}
public static object getpropertyvalue(object obj, string name)
{
propertyinfo property = obj.gettype().getproperty(name);
if (property != null)
{
object drv1 = property.getvalue(obj, null);
return drv1;
}
else
{
return null;
}
}
如代码所示,首先我们定义了一个kiba的对象,并为name赋值,然后我们通过getpropertyvalue方法,传递了kiba对象和要获取值的属性名称。
getpropertyvalue函数里通过使用propertyinfo完成了反射。
有的同学可能会觉得,这个很鸡肋,既然已经得到对象,还反射做什么,直接获取就可以了呀。
别着急,我们接下来一起看反射的架构应用。
反射的架构应用
框架编写的核心目的之一,是统一系统秩序。那么什么是系统秩序呢?
首先我们看下系统的构成,系统个通常是由子系统,程序集,类,函数这四部分构成。如下图所示。
既然系统由子系统,程序集,类,函数这四个基础元素构成,那么系统秩序,自然指的就是这四个元素的秩序。而这四个元素最难形成秩序的就是函数了。
很显然,任何的项目都存在重复的函数,或者功能相近的函数。而彻底杜绝这种情况,显然是不可能的。那么我们只好尽量是设计会避免重复元素的框架了。而反射,正是为此而存在的。
反射的架构应用
现实中的框架因为这样那样的原因,会有千奇百怪的设计,所以拘泥于一种设计模式是愚蠢的,实战中要多种设计模式一起应用,局部设计有时候只取设计模式中一部分也可以。这样才能实现项目的量身定制。
所以,这里只介绍一种实战的架构应用,一种使用反射的框架基础结构。下面请框架基础代码。
public class client
{
public void excutegetnamecommand()
{
proxy proxy = new proxy();
getnamecommand cmd = new getnamecommand();
resultbase rb = proxy.excutecommand(cmd);
}
}
public class proxy
{
public resultbase excutecommand(commandbase command)
{
var result = handlerswitcher.excute(command);
return result as resultbase;
}
}
public class handlerswitcher
{
private const string methodname = "excute";//约定的方法名
private const string classnamepostfix = "handler";//约定的处理command的类的名称的后缀
//获取命名空间的名称
public static string getnamespace(commandbase command)
{
type commandtype = command.gettype();//获取完全限定名
string[] commandtypenames = commandtype.tostring().split('.');
string namespace = "";
for (int i = 0; i < commandtypenames.length - 1; i++)
{
namespace += commandtypenames[i];
if (i < commandtypenames.length - 2)
{
namespace += ".";
}
}
return namespace;
}
public static object excute(commandbase command)
{
string fullname = command.gettype().fullname;//完全限定名
string namespace = getnamespace(command);//命名空间
assembly assembly = assembly.load(namespace);
type handlertype = assembly.gettype(fullname + classnamepostfix, true, false);
object obj = assembly.createinstance(fullname + classnamepostfix);
methodinfo handlemethod = handlertype.getmethod(methodname);//获取函数基本信息
object[] pmts = new object[] { command }; //传递一个参数command
try
{
return handlemethod.invoke(obj, pmts);
}
catch (targetinvocationexception tie)
{
throw tie.innerexception;
}
}
}
public class getnamecommandhandler
{
public resultbase excute(commandbase cmd)
{
getnamecommand command = (getnamecommand)cmd;
resultbase result = new resultbase();
result.message = "i'm kiba518";
return result;
}
}
public class getnamecommand: commandbase
{
}
public class commandbase
{
public int userid { get; set; }
public string username { get; set; }
public string argip { get; set; }
}
public class resultbase
{
public string message { get; set; }
}
代码中框架很简单,主要目的是实现一个代理,用于处理继承了commandbase的类的代理。
即,客户端,不论传来什么样的command,只要它是继承自commandbase的,这个代理都会找到对应的处理类,并执行处理,且返回结果。
为了更清晰的理解这段代码,我们可以参考下面这个流程图。结合了图片在来看代码,框架结构就会更清晰。
这个简单的框架中,使用了一个概念,叫做约定优先原则,也叫做约定优于配置;喜欢概念的小伙伴可以自行百度。
框架中使用的两个约定如下:
第一个是,处理command的类必须后缀名是command的类名+handler结尾。
第二个是,处理command的类中的处理函数名必须为excute。
其实概念就是供大家使用的,会用即可;学习的过程中,概念之类的术语,有个印象即可。
ps:为了阅读方便,这里面的类都集中写在了一个命名空间之下了,如果有想使用这种设计模式的同学,请按照自己项目所需进行扩展。
----------------------------------------------------------------------------------------------------
这样,我们就通过反射实现了一个非常简约的框架,通过使用这个框架,会让代码变的更加简洁。
而为了实现每个模块的简洁,反射也将会被封装在各个模块的底层,所以,反射毫无疑问,就是框架设计的基础。
反射与特性
反射在系统中另一个重要应用就是与特性的结合使用。
在一些相对复杂的系统中,难免会遇到一些场景,要讲对象中的一部分属性清空,或者要获取对象中的某些属性赋值。通常我们的实现方式就是手写,一个一个的赋值。
而利用反射并结合特性,完全可以简化这种复杂操作的代码量。
public partial class reflectionsyntax
{
public void excutekibaattribute()
{
kiba kiba = new kiba();
kiba.clearname = "kiba518";
kiba.noclearname = "kiba518";
kiba.normalname = "kiba518";
clearkibaattribute(kiba);
console.writeline(kiba.clearname);
console.writeline(kiba.noclearname);
console.writeline(kiba.normalname);
}
public void clearkibaattribute(kiba kiba)
{
list<propertyinfo> plist = typeof(kiba).getproperties(system.reflection.bindingflags.instance | system.reflection.bindingflags.public).tolist();//只获取public的属性
foreach (propertyinfo pinfo in plist)
{
var attrs = pinfo.getcustomattributes(typeof(kibaattribute), false);
if (null != attrs && attrs.length > 0)
{
var des = ((kibaattribute)attrs[0]).description;
if (des == "clear")
{
pinfo.setvalue(kiba, null);
}
}
}
}
}
public class kiba
{
[kibaattribute("clear")]
public string clearname { get; set; }
[kibaattribute("noclear")]
public string noclearname { get; set; }
public string normalname { get; set; }
}
[system.attributeusage(system.attributetargets.all)]
public class kibaattribute : system.attribute
{
public string description { get; set; }
public kibaattribute(string description)
{
this.description = description;
}
}
如上述代码所示, 我们通过反射,将拥有kibaattribute特性的,且描述为clear的属性,清空了。
当然为了一个属性这么做不值得,但如果一个对象有70个属性的时候,这么做就值得了。
既然能清除属性的数据,那么自然就可以为属性赋值。至于如何实现反射赋值,相信大家可以举一反三。
反射+特性最常见的场景
反射+特性一起应用,最常见的场景就是用ado.net从数据库查询出datatable的数据,然后将datatable的数据转换成model实体类型。
我们在开发中,为了让实体更加充血,往往会对数据实体增加一些属性和方法。(什么是充血?充血就是充血模型,有兴趣的同学可以自行百度了解下,简单说就是为实体加属性和方法。)
那么,在用反射,将datatable转存到model实体的时候,遍历属性并赋值的时候,就会多遍历那么几次。
如果只是一个实体,那么,多遍历几次也没影响。但,如果是数十万的数据,那这多几次的遍历影响就大了。
而用反射+特性,就可以减少这些额外遍历次数。
讲了这么多为什么不给代码呢?
因为我觉得,将上面的内容全理解的同学,应该可以说,已经框架启蒙了。那么,这个反射+特性的datatable转数据实体,如果能自己写出来,就算是框架入门了。所以,这里给大家留下了一个练习的空间。
注意,我这里说的是框架,而不是架构。
框架与架构的区别是这样的,框架是个名词,而架构是个动词。框架即便很熟练了,也不见得可以架构的很好。这个大家还是要注意区别。
结语
看完了整篇文章,有的同学可能会有疑问,这么生疏的propertyinfo和methodinfo真的有人会用吗?都是copy代码,然后使用吧。
答案是,当然有人可以熟练应用。反射是架构师的入门基础,任何一个[可以实战]的架构师,都需要随时随地的可以手写出反射,因为优化框架是他们的责任。
所以,对此有所怀疑的小伙伴,可以努力练习了,将委托融入血液,是高级软件工程师的基础,而将反射融入血液,就是架构师的基础了。
----------------------------------------------------------------------------------------------------
注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
若您觉得这篇文章还不错,请点击下右下角的【推荐】,非常感谢!
如果您觉得这篇文章对您有所帮助,那就不妨支付宝小小打赏一下吧。
上一篇: JS中如何获取url中的某个参数的值
下一篇: .net core 高性能对象转换