C# 特性与反射

特性

CLR via C#Professional C#书中都有提到,在C#编译器对特性进行编译时,会将其写入程序集的元数据中,所以特性的第一个显而易见的作用就是对代码的说明,相当于代码注解。但是,特性的功能远不止如此,这也是特性的第二个功能,特性结合反射,能够在程序运行时,读取特性的元数据,从而影响程序的运行。
C# 中的特性也是一个Class,在自定义一个特性时,需要指出以下几点:

  • 能够应用此特性的元素类型,可以使用AttributeTargets枚举来定义;
  • 对于同一个元素,能否多次应用这个特性,用AllowMultiple来指定;
  • 当特性应用于Class或者Interface时,特性能否由子类继承,用`Inherited``指定;

例如有一个自定义特性如下:

using System;

namespace AttributeAndReflection
{
    [AttributeUsage(AttributeTargets.Class, 
        AllowMultiple = false, Inherited = false)]
    public class FieldNameAttribute: Attribute
    {
        private string _name;

        public string Name => _name;
        public FieldNameAttribute(string name)
        {
            _name = name;
        }
    }
}

反射:特性

接下来,我们使用反射来解析使用了上述特性的一个类。首先,在Program上使用了上述自定义的一个特性,创建了FieldNameAttribute的一个实例,并用“ClassTest”来初始化_name。然后在Main函数中,通过反射获取了这个特性的实例,最后输出了实例中的Name属性。

using System.Reflection;

namespace AttributeAndReflection
{
    [FieldName("ClassTest")]
    internal class Program
    {
        public static void Main(string[] args)
        {
            Type programType = typeof(Program);
            FieldNameAttribute attribute= (FieldNameAttribute)programType.GetTypeInfo().GetCustomAttribute(typeof(FieldNameAttribute));
            Console.WriteLine(attribute.Name);
            Console.ReadLine();
        }
    }
}

反射:动态加载程序集

除了可以动态解析特性,反射还可以动态加载程序集,实例化对象,接下来给出一个这样的例子,例子包括两个程序集。

  • CalculatorLib.dll,该程序集定义了Calculator类型;
  • ClientApp.exe,该控制台应用程序动态使用Calculator的方法。
// Calculator类型定义
public class Calculator 
{ 
	public double Add(double x, double y) => x + y; 
	public double Subtract(double x, double y) => x - y; 
}

Program程序是一个命令行程序,程序会有一个启动参数,用来表示Calculator.dll程序集所在的位置。
为了动态使用CalculatorLib.Calculator.Add方法,ClientApp.Program.Main方法中首先得加载CalculatorLib程序集,其次创建出Calculator实例,然后使用这个实例的calc.GetType().GetMethod()方法获取Add方法对应的MethodInfo对象,从而调用其Invoke方法,该方法的第一个参数是Add方法所属的实例,第二个参数是一个object[]数组,用来传递参数到Add方法中,具体代码如下。

using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Reflection;

namespace ClientApp
{
    class Program
    {
        private const string CalculatorTypeName = "CalculatorLib.Calculator";

        static void Main(string[] args)
        {
            if (args.Length != 1)
            {
                ShowUsage();
                return;
            }
            UsingReflection(args[0]);
        }

        private static void ShowUsage()
        {
            Console.WriteLine($"Usage: {nameof(ClientApp)} path");
            Console.WriteLine();
            Console.WriteLine("Copy CalculatorLib.dll to an addin directory");
            Console.WriteLine("and pass the absolute path of this directory when starting the application to load the library");
        }

        private static void UsingReflection(string addinPath)
        {
            double x = 3;
            double y = 4;
            object calc = GetCalculator(addinPath);

            object result = calc.GetType().GetMethod("Add").Invoke(calc, new object[] { x, y });
            Console.WriteLine($"the result of {x} and {y} is {result}");
        }

        private static object GetCalculator(string addinPath)
        {
            Assembly assembly = Assembly.LoadFile(addinPath);
            return assembly.CreateInstance(CalculatorTypeName);
        }
    }
}

dynamic:动态使用程序集

当然,C#提供了一种更加简化的方式来动态加载一个程序集,使用其中定义的类型,这就是dynamic关键字。通过dynamic关键字定义的Calculator实例,可以直接使用其方法,完成calc.Add(x, y)的调用,这段程序在VS或者Rider编辑器中是没有智能提示的。dynamic关键字定义的类型会跳过编译时的检查,因此需要在运行时处理其可能发生的错误,下面代码中使用了Calculator中没有定义的Multiply方法,在运行时,我们可以捕获到RuntimeBinderException

using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Reflection;

namespace ClientApp
{
    class Program
    {
        private const string CalculatorTypeName = "CalculatorLib.Calculator";

        static void Main(string[] args)
        {
            if (args.Length != 1)
            {
                ShowUsage();
                return;
            }
            UsingReflectionWithDynamic(args[0]);
        }

        private static void ShowUsage()
        {
            Console.WriteLine($"Usage: {nameof(ClientApp)} path");
            Console.WriteLine();
            Console.WriteLine("Copy CalculatorLib.dll to an addin directory");
            Console.WriteLine("and pass the absolute path of this directory when starting the application to load the library");
        }

        private static void UsingReflectionWithDynamic(string addinPath)
        {
            double x = 3;
            double y = 4;
            dynamic calc = GetCalculator(addinPath);
            double result = calc.Add(x, y);
            Console.WriteLine($"the result of {x} and {y} is {result}");

            try
            {
                result = calc.Multiply(x, y);
            }
            catch (RuntimeBinderException ex)
            {
                Console.WriteLine(ex);
            }
        }
        private static object GetCalculator(string addinPath)
        {
            Assembly assembly = Assembly.LoadFile(addinPath);
            return assembly.CreateInstance(CalculatorTypeName);
        }
    }
}

你可能感兴趣的:(c#,反射)