欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

关于C#反射 你需要知道的

程序员文章站 2022-03-07 12:52:24
通常,反射用于动态获取对象的类型、属性和方法等信息。今天带你玩转反射,来汇总一下反射的各种常见操作,捡漏看看有没有你不知道的。获取类型的成员type 类的 getmembers 方法用来获取该类型的所...

通常,反射用于动态获取对象的类型、属性和方法等信息。今天带你玩转反射,来汇总一下反射的各种常见操作,捡漏看看有没有你不知道的。

获取类型的成员

type 类的 getmembers 方法用来获取该类型的所有成员,包括方法和属性,可通过 bindingflags 标志来筛选这些成员。

using system;
using system.reflection;
using system.linq;

public class program
{
  public static voidmain()
  {
    var members = typeof(object).getmembers(bindingflags.public |
      bindingflags.static | bindingflags.instance);
    foreach (var member in members)
    {
      console.writeline($"{member.name} is a {member.membertype}");
    }
  }
}

输出:

gettype is a method
gethashcode is a method
tostring is a method
equals is a method
referenceequals is a method
.ctor is a constructor

getmembers 方法也可以不传 bindingflags,默认返回的是所有公开的成员。

获取并调用对象的方法

type 类型的 getmethod 方法用来获取该类型的 methodinfo,然后可通过 methodinfo 动态调用该方法。

对于非静态方法,需要传递对应的实例作为参数,示例:

class program
{
  public static void main()
  {
    var str = "hello";
    var method = str.gettype()
      .getmethod("substring", new[] {typeof(int), typeof(int)});
    var result = method.invoke(str, new object[] {0, 4}); // 相当于 str.substring(0, 4)
    console.writeline(result); // 输出:hell
  }
}

对于静态方法,则对象参数传空,示例:

var method = typeof(math).getmethod("exp");
// 相当于 math.exp(2)
var result = method.invoke(null, new object[] {2});
console.writeline(result); // 输出(e^2):7.38905609893065

如果是泛型方法,则还需要通过泛型参数来创建泛型方法,示例:

class program
{
  public static void main()
  {
    // 反射调用泛型方法
    methodinfo method1 = typeof(sample).getmethod("genericmethod");
    methodinfo generic1 = method1.makegenericmethod(typeof(string));
    generic1.invoke(sample, null);

    // 反射调用静态泛型方法
    methodinfo method2 = typeof(sample).getmethod("staticmethod");
    methodinfo generic2 = method2.makegenericmethod(typeof(string));
    generic2.invoke(null, null);
  }
}

public class sample
{
  public void genericmethod<t>()
  {
    //...
  }
  public static void staticmethod<t>()
  {
    //...
  }
}

创建一个类型的实例

使用反射动态创建一个类型的实例有多种种方式。最简单的一种是用 new() 条件声明。

使用 new 条件声明

如果在一个方法内需要动态创建一个实例,可以直接使用 new 条件声明,例如:

t getinstance<t>() where t : new()
{
  t instance = newt();
  return instance;
}

但这种方式适用场景有限,比如不适用于构造函数带参数的类型。

使用 activator 类

使用 activator 类动态创建一个类的实例是最常见的做法,示例:

type type = typeof(biginteger);
object result = activator.createinstance(type);
console.writeline(result); // 输出:0
result = activator.createinstance(type, 123);
console.writeline(result); // 输出:123

动态创建泛类型实例,需要先创建开放泛型(如list<>),再根据泛型参数转换为具象泛型(如list<string>),示例:

// 先创建开放泛型
type opentype = typeof(list<>);
// 再创建具象泛型
type[] targs = { typeof(string) };
type target = opentype.makegenerictype(targs);
// 最后创建泛型实例
list<string> result = (list<string>)activator.createinstance(target);

如果你不知道什么是开放泛型和具象泛型,请看本文最后一节。

使用构造器反射

也可以通过反射构造器的方式动态创建类的实例,比上面使用 activator 类要稍稍麻烦些,但性能要好些。示例:

constructorinfo c = typeof(t).getconstructor(new[] { typeof(string) });
if (c == null)
  throw new invalidoperationexception("...");
t instance = (t)c.invoke(new object[] { "test" });

使用 formatterservices 类

如果你想创建某个类的实例的时候不执行构造函数和属性初始化,可以使用 formatterservices 的 getuninitializedobject 方法。示例:

class program
{
  static void main()
  {
    myclass instance = (myclass)formatterservices.getuninitializedobject(typeof(myclass));
    console.writeline(instance.myproperty1); // 输出:0
    console.writeline(instance.myproperty2); // 输出:0
  }
}

public class myclass
{
  public myclass(int val)
  {
    myproperty1 = val < 1 ? 1 : val;
  }

  public int myproperty1 { get; }

  public int myproperty2 { get; set; } = 2;
}

获取属性或方法的强类型委托

通过反射获取到对象的属性和方法后,如果你想通过强类型的方法来访问或调用,可以在中间加一层委托。这样的好处是有利于封装,调用者可以明确的知道调用时需要传什么参数。 比如下面这个方法,把 math.max 方法提取为一个强类型委托:

var targs = new type[] { typeof(int), typeof(int) };
var maxmethod = typeof(math).getmethod("max", targs);
var strongtypedelegate = (func<int, int, int>)delegate
  .createdelegate(typeof(func<int, int, int>), null, maxmethod);
console.writeline("3 和 5 之间最大的是:{0}", strongtypedelegate(3, 5)); // 输出:5

这个技巧也适用于属性,可以获取强类型的 getter 和 setter。示例:

var theproperty = typeof(myclass).getproperty("myintproperty");

// 强类型 getter
var thegetter = theproperty.getgetmethod();
var strongtypegetter = (func<myclass, int>)delegate
  .createdelegate(typeof(func<myclass, int>), thegetter);
var intval = strongtypegetter(target); // 相关于:target.myintproperty

// 强类型 setter
var thesetter = theproperty.getsetmethod();
var strongtypesetter = (action<myclass, int>)delegate
  .createdelegate(typeof(action<myclass, int>), thesetter);
strongtypesetter(target, 5); // 相当于:target.myintproperty = 5

反射获取自定义特性

以下是四个常见的场景示例。

示例一,找出一个类中标注了某个自定义特性(比如 myatrribute)的属性。

var props = type
  .getproperties(bindingflags.nonpublic | bindingflags.public | bindingflags.instance)
  .where(prop =>attribute.isdefined(prop, typeof(myattribute)));

示例二,找出某个属性的所有自定义特性。

var attributes = typeof(t).getproperty("name").getcustomattributes(false);

示例三,找出程序集所有标注了某个自定义特性的类。

static ienumerable<type> gettypeswithattribute(assembly assembly)
{
  foreach(type type inassembly.gettypes())
  {
    if (type.getcustomattributes(typeof(myattribute), true).length > 0)
    {
      yield return type;
    }
  }
}

示例四,在运行时读取自定义特性的值

public static class attributeextensions
{
  public static tvalue getattribute<tattribute, tvalue>(
    this type type,
    string membername,
    func<tattribute, tvalue> valueselector,
    bool inherit = false)
    where tattribute : attribute
  {
    var att = type.getmember(membername).firstordefault()
      .getcustomattributes(typeof(tattribute), inherit)
      .firstordefault() as tattribute;
    if (att != null)
    {
      return valueselector(att);
    }
    return default;
  }
}

// 使用:

class program
{
  static void main()
  {
    // 读取 myclass 类的 mymethod 方法的 description 特性的值
    var description = typeof(myclass)
      .getattribute("mymethod", (descriptionattribute d) => d.description);
    console.writeline(description); // 输出:hello
  }
}
public class myclass
{
  [description("hello")]
  public void mymethod() { }
}

动态实例化接口的所有实现类(插件激活)

通过反射来动态实例化某个接口的所有实现类,常用于实现系统的插件式开发。比如在程序启动的时候去读取指定文件夹(如 plugins)中的 dll 文件,通过反射获取 dll 中所有实现了某个接口的类,并在适当的时候将其实例化。大致实现如下:

interface iplugin
{
  string description { get; }
  void dowork();
}

某个在独立 dll 中的类:

class helloplugin : iplugin
{
  public string description => "a plugin that says hello";
  public void dowork()
  {
    console.writeline("hello");
  }
}

在你的系统启动的时候动态加载该 dll,读取实现了 iplugin 接口的所有类的信息,并将其实例化。

public ienumerable<iplugin> instantiateplugins(string directory)
{
  var assemblynames = directory.getfiles(directory, "*.addin.dll")
    .select(name => new fileinfo(name).fullname).toarray();

  foreach (var filename assemblynames)
    appdomain.currentdomain.load(file.readallbytes(filename));

  var assemblies = assemblynames.select(system.reflection.assembly.loadfile);
  var typesinassembly = assemblies.selectmany(asm =>asm.gettypes());
  var plugintypes = typesinassembly.where(type => typeof (iplugin).isassignablefrom(type));

  return plugintypes.select(activator.createinstance).cast<iplugin>();
}

检查泛型实例的泛型参数

前文提到了构造泛型和具象泛型,这里解释一下。大多时候我们所说的泛型都是指构造泛型,有时候也被称为具象泛型。比如 list<int> 就是一个构造泛型,因为它可以通过 new 来实例化。相应的,list<> 泛型是非构造泛型,有时候也被称为开放泛型,它不能被实例化。开放泛型通过反射可以转换为任意的具象泛型,这一点前文有示例。

假如现在有一个泛型实例,出于某种需求,我们想知道构建这个泛型实例需要用什么泛型参数。比如某人创建了一个 list<t> 泛型的实例,并把它作为参数传给了我们的一个方法:

var mylist = newlist<int>();
showgenericarguments(mylist);

我们的方法签名是这样的:

public void showgenericarguments(object o)

这时,作为此方法的编写者,我们并不知道这个 o 对象具体是用什么类型的泛型参数构建的。通过反射,我们可以得到泛型实例的很多信息,其中最简单的就是判断一个类型是不是泛型:

public void showgenericarguments(object o)
{
  if (o == null) return;
  type t =o.gettype();
  if (!t.isgenerictype) return;
  ...
}

由于 list<> 本身也是泛型,所以上面的判断不严谨,我们需要知道的是对象是不是一个构造泛型(list<int>)。而 type 类还提供了一些有用的属性:

typeof(list<>).isgenerictype // true
typeof(list<>).isgenerictypedefinition // true
typeof(list<>).isconstructedgenerictype// false

typeof(list<int>).isgenerictype // true
typeof(list<int>).isgenerictypedefinition // false
typeof(list<int>).isconstructedgenerictype// true

isconstructedgenerictype isgenerictypedefinition 分别用来判断某个泛型是不是构造泛型和非构造泛型。

再结合 type 的 getgenericarguments() 方法,就可以很容易地知道某个泛型实例是用什么泛型参数构建的了,例如:

static void showgenericarguments(object o)
{
  if (o == null) return;
  type t = o.gettype();
  if (!t.isconstructedgenerictype) return;
  foreach (type generictypeargument in t.getgenericarguments())
    console.writeline(generictypeargument.name);
}

以上就是深入了解c#之反射的详细内容,更多关于c# 反射的资料请关注其它相关文章!

相关标签: C# 反射