为反射 使用 动态语言扩展
前面 一直 使用反射 来读取 元数据。还可以 使用 反射 从编译时 还不清楚的类型中 动态创建 实例
- 编译器在编译时 不知道这种类型:
- 程序集 是动态加载的,没有添加引用
- 在运行期间,实例化 对象,调用方法
- 知道 如何使用 Reflection API 后, C# 关键字
dynamic可以完成 相同的操作- C# 4.0 以来,就有dynamic关键字
示例
- 要使用 反射 动态创建 Calculator 实例
- 并使用两种方式 调用 Calculator 实例的方法
namespace CalculatorLib
{
// This project can output the Class library as a NuGet Package.
// To enable this option, right-click on the project and select the Properties menu item. In the Build tab select "Produce outputs on build".
public class Calculator
{
public double Add(double x, double y) => x + y;
public double Subtract(double x, double y) => x - y;
}
}
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]);
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 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);
}
}
}
其中:
调用方法之前,需要实例化 Calculator
private static object GetCalculator(string addinPath)
{
Assembly assembly = Assembly.LoadFile(addinPath);
return assembly.CreateInstance(CalculatorTypeName);
}
Assembly.LoadFile()动态加载 程序集 (也就是 调用者 事先没有添加 对这个程序集的 引用)- 参数是:Calculator的库的路径
- 返回值:
Assembly实例
Assembly的实例方法CreateInstance()- 参数是:类名 (包括命名空间)
- 返回值:实例
用 Reflection API 来调用 Calculator的方法:
优点是:
- 类型 不需要 在编译期间 可用
- 只要 把库复制到 指定目录中,就可以在 稍后添加它
为了 使用 反射 调用成员, 需要用
GetType()得到 实例的Type对象
object calc = GetCalculator(addinPath);
//这里传递"Add"这个名字,编译时并不检查这个方法是否存在
object result = calc.GetType().GetMethod("Add").Invoke(calc, new object[] { x, y });
MethodInfo对象的Invoke()方法- 第一个参数:需要调用成员的类型实例
- 其他参数:
object[]- 任意多个参数,传给方法
用 动态类型 调用 Calculator的方法:
- 将 Calculator 实例 赋给
dynamic类型的变量
dynamic calc = GetCalculator(addinPath);
double result = calc.Add(x, y);
优点:
- 语法简单
- 在特定情形下有优势:
- 反射。例如这个例子中,
缺点:
- 看起来像是用强类型访问方式调用一个方法,但实际并不是
- 没有在编译时进行检查
- 有问题的话,运行时才会暴露
- 与强类型方式访问对象相比,
dynamic类型有更多开销
dynamic 类型
dynamic类型 允许 编写 忽略编译期间 类型检查 的代码
- 编译器假定,给
dynamic类型的对象 定义的任何操作 都是有效的 - 如果该操作无效,则在代码运行之前 不会检测该错误
- 运行时 错误为
RuntimeBinderException
dynamic类型的两个限制:- 不支持扩展方法
- LINQ不能用
dynamic类型 后台的实现:- 编译器在编译为IL时做了很多事,产生了更多IL代码
CallSite类用来在运行时查找
dynamic类型是有用的,但它是有代价的
比较 dynamic和var
dynamic的对象 可以 在运行期间 改变其类型- 可以改变
- 可以改变多次
- 这不同于 把对象的类型强制转换为另一种兼容的类型
- 这里 甚至可以把它从int 变成 Person类
- 使用
var时,对象类型的确定会延迟,但类型一旦确定,就不能改变
DynamicObject 和 ExpandoObject
要创建自己的动态对象,有两种办法:
- 从 DynamicObject 中派生
- 需要做的工作比较多
- 必须重写几个方法
- 使用ExpandoObject
- 是立即可用的密封类
DynamicObject 示例
重写了以下方法:
- TrySetMember
- 给对象 添加 新方法、属性或字段
- 本例 将添加的东西 存在Dictionary里
- SetMemberBinder的Name 用于 标识Dictionary里的东西
- TryGetMember
- 根据GetMemberBinder的Name 从Dictionary里检索东西
using System;
using System.Collections.Generic;
using System.Dynamic;
namespace DynamicSample
{
public class WroxDynamicObject : DynamicObject
{
private Dictionary<string, object> _dynamicData = new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
bool success = false;
result = null;
if (_dynamicData.ContainsKey(binder.Name))
{
result = _dynamicData[binder.Name];
success = true;
}
else
{
result = "Property Not Found!";
}
return success;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_dynamicData[binder.Name] = value;
return true;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
dynamic method = _dynamicData[binder.Name];
result = method((DateTime)args[0]);
return result != null;
}
}
}
- 可以直接使用FirstName和LastName 就好像它们一直存在一样。
- 其实是DynamicObject处理了绑定
dynamic dyn;
dyn = new WroxDynamicObject();
dyn.FirstName = "Bugs";
dyn.LastName = "Bunny";
Console.WriteLine(dyn.GetType());
Console.WriteLine($"{dyn.FirstName} {dyn.LastName}");
dynamic dyn;
Func<DateTime, string> GetTomorrow = today => today.AddDays(1).ToString("d");
dyn.GetTomorrowDate = GetTomorrow;
Console.WriteLine("Tomorrow is {0}", dyn.GetTomorrowDate(DateTime.Now));