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

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

获取类型的成员

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# 反射的资料请关注我们其它相关文章!

(0)

相关推荐

  • .NET/C#利用反射调用含ref或out参数的方法示例代码

    前言 使用反射,我们可以很容易地在运行时调用一些编译时无法确定的属性.方法等.在.NET中的反射可以实现从对象的外部来了解对象(或程序集)内部结构的功能,哪怕你不知道这个对象(或程序集)是个什么东西,另外.NET中的反射还可以运态创建出对象并执行它其中的方法. 反射是.NET中的重要机制,通过反射,可以在运行时获得程序或程序集中每一个类型(包括类.结构.委托.接口和枚举等)的成员和成员的信息.有了反射,即可对每一个类型了如指掌.另外我还可以直接创建对象,即使这个对象的类型在编译时还不知道. 反射

  • .NET/C#如何使用反射注册事件详解

    前言 通过放射,可以在运行时获得.NET中每一个类型(包括类.结构.委托.接口和枚举等)的成员,包括方法.属性.事件,以及构造函数等.还可以获得每个成员的名称.限定符和参数等.有了反射,即可对每一个类型了如指掌.如果获得了构造函数的信息,即可直接创建对象,即使这个对象的类型在编译时还不知道.那么如何注册事件呢? 本文将介绍如何使用反射注册事件.下面话不多说了,来一起看看看详细的介绍吧 不使用反射 例如,我们希望反射的类型是这样的: public class Walterlv { public e

  • 详解C# 利用反射根据类名创建类的实例对象

    "反射"其实就是利用程序集的元数据信息. 反射可以有很多方法,编写程序时请先导入 System.Reflection 命名空间. 1.假设你要反射一个 DLL 中的类,并且没有引用它(即未知的类型): Assembly assembly = Assembly.LoadFile("程序集路径,不能是相对路径"); // 加载程序集(EXE 或 DLL) dynamic obj = assembly.CreateInstance("类的完全限定名(即包括命名空

  • C#基础学习系列之Attribute和反射详解

    前言 本文主要给大家介绍了关于C#基础之Attribute和反射的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. Attribute(特性) Attribute是C#的一种语言特性,用于为各种实体(class,field,property)附加一些说明性信息, 并且可以在运行时环境中检索这些信息(通过反射). 所有的Attribute必须继承自Attribute类,按照约定,特性类的名称带有 Attribute 后缀.使用特性时可以包含或省略此后缀. Attribut

  • C#高效反射调用方法类实例详解

    C#高效反射调用方法类 1.创建一个业务类(HomeService),在类下创建3个方法 2.正常方式调用类的方法 3.反射方式调用类的方法 4.调用代码 5.调用结果 6.Service类方法代码 内容扩展: 1.正常方式调用类的方法 /// <summary> /// 正常调用类的方法(parm1) /// </summary> /// <returns></returns> public string GetNormalMethod_2() { Hom

  • C# 使用反射来实现对象的深度复制方法

    实现方式 通过挨个罗列的方式一次复制子对象是非常耗费人力的,如果子对象是引用类型,则还要需要考虑是否对子对象进一步深拷贝. 实际应用中,一个类如果有几十个子对象,挨个复制对于开发人员来说索然无味比较费时费力. 所以使用反射机制来实现.   但是如果是服务端运行的话,还是建议手动的实现. 毕竟反射机制比直接写出来的效率要慢一些. 代码: public static class DeepCopyHelper { public static object Copy(this object obj) {

  • C#基于Linq和反射实现数据持久化框架Xml4DB详解

    我们知道目前大部分的数据库都是关系型数据库, 所谓关系型数据库,就是指建立在关系模型 基础之上的数据库系统,如Oracle.SQL Server.Access.MySQL等.关系模型就是指二维表格模型,因而一个关系型数据库就是由二维表及其之间的联系组成的一个数据组织.一个偶然的机会我接触到了DB4O,它是一个完全面向对象的开源数据库,它的出现完全颠覆了传统的数据库在人们心中的形象,因为传统的数据库需要在数据体.实体之间转换,而且需要映射文件提供映射关系.正是这个项目让我产生了编写Xml4DB的想

  • C#使用反射(Reflect)获取dll文件中的类型并调用方法

    使用反射(Reflect)获取dll文件中的类型并调用方法,具体内容如下 需引用:System.Reflection; 1. 使用反射(Reflect)获取dll文件中的类型并调用方法(入门案例) static void Main(string[] args) { //dll文件路径 string path = @"D:\VS2015Project\001\Computer\bin\Debug\computer.dll"; //加载dll文件 Assembly asm = Assemb

  • c#使用dynamic类型优化反射的方法

    什么是dynamic类型? 微软给出的官方文档中这样解释:在通过 dynamic 类型实现的操作中,该类型的作用是绕过编译时类型检查. 改为在运行时解析这些操作. dynamic 类型简化了对 COM API(例如 Office Automation API).动态 API(例如 IronPython 库)和 HTML 文档对象模型 (DOM) 的访问.在大多数情况下,dynamic 类型与 object 类型的行为类似. 但是,如果操作包含 dynamic 类型的表达式,那么不会通过编译器对该

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

    通常,反射用于动态获取对象的类型.属性和方法等信息.今天带你玩转反射,来汇总一下反射的各种常见操作,捡漏看看有没有你不知道的. 获取类型的成员 Type 类的 GetMembers 方法用来获取该类型的所有成员,包括方法和属性,可通过 BindingFlags 标志来筛选这些成员. using System; using System.Reflection; using System.Linq; public class Program { public static voidMain() {

  • PHP小白必须要知道的php基础知识(超实用)

    很多人看到PHP就以为是程序员,就以为钱很多(虽然是事实),但是也要考虑下自己是不是适合这一行,知道PHP是什么吗?PHP都有什么样的功能,都能用来干嘛? PHP是什么? •PHP(PHP: Hypertext Preprocessor,超文本预处理器的缩写),是一 种被广泛应用的开放源代码的.基于服务器端的用于产生动态网页 的.可嵌入HTML中的脚本程序语言,尤其适合 WEB 开发. •当客户端向服务器的程序提出请求时,web服务器根据请求晌应对应 的页面,当页面中含有php脚本时,服务器会交

  • 分享下网站开发人员应该知道的61件事

    不出意料地,他得到了一大堆回答. 通常情况下,你需要把所有人的发言从头到尾读一遍.但是,Stack Overflow有一个很贴心的设计,它允许在问题下方开设一个wiki区,让所有人共同编辑一个最佳答案.于是,就有了下面这篇文章,一共总结出六个方面共计61条"网站开发须知". 我发现,这种概述性的问题,最适合这种集合群智.头脑风暴式的回答方式了.这也是我第一次觉得,Stack Overflow做到了Wikipedia做不到的事.(难怪它最近挤进了全美前400大网站.) 在我的印象中,关于

  • Web开发/设计人员应当知道的15个网站

    ColorCombos 在进行网站设计的时候,开始步骤的其中一项(也是最重要)的内容是选择出一个配色方案. Color Combos让你可以浏览上千个不同的颜色组合,以便从中为你即将开始的设计汲取灵感.其配色方案可按颜色浏览. LIpsum 风靡之至的Lorem Ipsum文字,其大名谁人不知?哪个不晓?(译注:查了才知道,这是指一篇用于测试排版设计的拉丁文文章,从15世纪开始就被广泛使用,文章因以Lorem Ipsum开头而得名)该段文字被全球各地的网站设计者用来在设计中对其真正的文字的呈现效

  • PHP 开发者该知道的 5 个 Composer 小技巧

    Composer 是新一代的PHP依赖管理工具.其介绍和基本用法可以看这篇<Composer PHP依赖管理的新时代>.本文介绍使用Composer的五个小技巧,希望能给你的PHP开发带来方便. 1. 仅更新单个库 只想更新某个特定的库,不想更新它的所有依赖,很简单: composer update foo/bar 此外,这个技巧还可以用来解决"警告信息问题".你一定见过这样的警告信息: Warning: The lock file is not up to date wi

  • 你应该知道的Python3.6、3.7、3.8新特性小结

    很多人在学习了基本的Python语言知识后,就转入应用阶段了,后期很少对语言本身的新变化.新内容进行跟踪学习和知识更新,甚至连已经发布了好几年的Python3.6的新特性都缺乏了解. 本文列举了Python3.6.3.7.3.8三个版本的新特性,学习它们有助于提高对Python的了解,跟上最新的潮流. 一.Python3.6新特性 1.新的格式化字符串方式 新的格式化字符串方式,即在普通字符串前添加 f 或 F 前缀,其效果类似于str.format().比如 name = "red"

  • 经验丰富程序员才知道的8种高级Python技巧

    本文将介绍8个简洁的Python技巧,若非经验十足的程序员,你肯定有些从未见过.向着更简洁更高效,出发吧! 1.通过多个键值将对象进行排序 假设要对以下字典列表进行排序: people = [ { 'name': 'John', "age": 64 }, { 'name': 'Janet', "age": 34 }, { 'name': 'Ed', "age": 24 }, { 'name': 'Sara', "age": 6

  • 每个 Python 开发者都应该知道的7种好用工具(效率翻倍)

    Python 从一种小的开源语言开始,到现在,它已经成为开发者很受欢迎的编程语言之一. 今天我将给大家分享 7 种对所有 Python 开发人员都感觉很有趣.有用的工具,相信它们在你的工作中会经常出现,提升工作效率. 1.The F*ck 当我们忘记了某些软件包在这里或那里,The F*ck优雅地解决了这个问题.你所要做的只是键入"Fuck",然后它会告诉你出了什么问题. 它是该列表上很受欢迎的项目,并且将继续存在.安装方法如下: # mac brew install thefuck

  • 7个你应该知道的JS原生错误类型

    概述 从浏览器控制台到运行 Node.js的终端,我们到处都会看到错误.本文的重点是概述我们在js开发过程中可能遇到的错误类型. 提示:良好的错误提示会导致快速而无痛的发展经历与缓慢而痛苦的发展经历之间的区别.在编写可重用的代码时,请确保自己在编写清晰易懂的错误处理代码. 1. RangeError 当数字超出允许的值范围时,将会抛出此错误. 例如 const l = console.logconst arr = [90,88] arr.length=90**99 我们有一个数组,带有两个元素的

  • 一定要知道的 25 个 Vue 技巧

    目录 1. 将 prop 限制为类型列表 2. 默认内容和扩展点 3. 使用引号观察嵌套值 4. 知道何时使用 v-if(以及何时避免使用) 5. 单作用域 slot 的简写(不需要模板标签!) 6. 有条件地渲染slot 6.1 为什么我们希望能够有条件地渲染slot呢? 7. 如何观察slot的变化 8. 将本地和全局风格混合在一起 9. 覆盖子组件的样式--正确的方法 10. 用上下文感知组件创造魔法 10.1 状态共享 10.2 配置 10.3 造型 11. 如何使在 Vue 之外创建的

随机推荐