当前位置: 移动技术网 > IT编程>开发语言>c# > 关于C#反射 你需要知道的

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

2020年06月14日  | 移动技术网IT编程  | 我要评论

安弘测试杯,烙铁芯,棕垫十大品牌

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

获取类型的成员

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# 反射的资料请关注移动技术网其它相关文章!

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网