我正在创建一个框架,其中包含一个围绕库(特别是SharpBrake)的包装器,它通过反射执行与SharpBrake的所有交互,因此对于我的框架的第三方来说库没有硬性依赖。
如果我的框架的第三方想要使用SharpBrake,他们可以将SharpBrake.dll填充到bin文件夹中,但如果他们不这样做,他们就可以忘掉它。如果我的框架明确引用了SharpBrake类型,那么我的框架用户会在SharpBrake.dll缺失的运行时期间遇到异常,这是我不想要的。
因此,我的包装器首先从磁盘加载SharpBrake.dll,找到AirbrakeClient
类型,并在私有字段中存储指向AirbrakeClient.Send(AirbrakeNotice)
方法的委托。但问题是,由于Send()
方法采用AirbrakeNotice
对象而我无法直接引用AirbrakeNotice
对象,因此我需要以某种方式转换Send()
方法到Action<object>
。
我强烈认为这是不可能的,但我想在探讨Delegate
并使用DynamicInvoke()
之前探索所有选项,我认为这远非最佳,性能方面。我想做的是以下几点:
Type clientType = exportedTypes.FirstOrDefault(type => type.Name == "AirbrakeClient");
Type noticeType = exportedTypes.FirstOrDefault(type => type.Name == "AirbrakeNotice");
MethodInfo sendMethod = clientType.GetMethod("Send", new[] { noticeType });
object client = Activator.CreateInstance(clientType);
Type actionType = Expression.GetActionType(noticeType);
Delegate sendMethodDelegate = Delegate.CreateDelegate(actionType, client, sendMethod);
// This fails with an InvalidCastException:
Action<object> sendAction = (Action<object>)sendMethodDelegate;
然而,这失败了以下例外:
System.InvalidCastException:无法将类型为'System.Action`1 [SharpBrake.Serialization.AirbrakeNotice]'的对象强制转换为'System.Action`1 [System.Object]'。
显然,因为sendMethodDelegate
是Action<AirbrakeNotice>
而不是Action<object>
。由于我在代码中不能提到AirbrakeNotice
,我不得不这样做:
Action<object> sendAction = x => sendMethodDelegate.DynamicInvoke(x);
或直接公开Delegate sendMethodDelegate
。这可能吗?我知道有可能遇到object
可能与AirbrakeNotice
不同类型的情况,这可能是坏的,但看看你有多少可以搞砸了反思,我希望有某处有漏洞。
答案 0 :(得分:6)
如果您乐意使用表达式树,那就相当简单了:
ConstantExpression target = Expression.Constant(client, clientType);
ParameterExpression parameter = Expression.Parameter(typeof(object), "x");
Expression converted = Expression.Convert(parameter, noticeType);
Expression call = Expression.Call(target, sendMethod, converted);
Action<object> action = Expression.Lambda<Action<object>>(call, parameter)
.Compile();
我认为这就是你想要的......
答案 1 :(得分:2)
如果您不需要低于C#4支持,则可以使用dynamic
vs DynamicInvoke
获得更高的性能。
Action<dynamic> sendAction = x => sendMethodDelegate(x);
实际上,如果您可以使用动态,我猜您甚至不需要上面的内容,因为如果您这样做,它会提高性能并简化所有内容:
Type clientType = exportedTypes.FirstOrDefault(type => type.Name == "AirbrakeClient");
dynamic client = Activator.CreateInstance(clientType);
...
client.Send(anAirbrakeNotice);
但是,如果你需要支持.net 3.5 jon双向调用表达式树的答案绝对是可行的方法。
答案 2 :(得分:1)
从我对OP的评论:
如果您担心性能,我会避免长时间使用反射。如果你能为你正在使用的类提供一个接口,那么我就创建一个接口。然后通过调用SharpBreak代码编写一个实现接口的包装器,并将其填入单独的DLL中。然后动态加载您的包装器程序集和具体的包装器类型,并调用该接口。然后你不必在方法级别做反射。
我不确定你需要的所有类,但这里有一个简单的例子,说明如何使用基于接口的松散耦合来挂钩该库。
在程序的程序集中:
public IExtensions
{
void SendToAirbrake(Exception exception);
}
public static AirbreakExtensions
{
private static IExtensions _impl;
static()
{
impl = new NullExtensions();
// Todo: Load if available here
}
public static void SendToAirbrake(this Exception exception)
{
_impl.SendToAirbrake(exception);
}
}
internal class NullExtensions : IExtensions // no-op fake
{
void SendToAirbrake(Exception exception)
{
}
}
在加载 - 如果可用(通过反射)程序集
中public ExtensionsAdapter : IExtensions
{
void SendToAirbrake(Exception exception)
{
SharpBrake.Extensions.SendToAirbrake(exception);
}
}
这种方法的优点是你只使用一次反射(在加载时),而不再使用它。修改它以使用依赖注入或模拟对象(用于测试)也很简单。
修改强>
对于其他类型,需要更多的工作。
您可能需要use the Abstract Factory pattern来实例化AirbrakeNoticeBuilder
,因为您需要直接处理接口,并且不能将构造函数放在接口中。
public interface IAirbrakeNoticeBuilderFactory
{
IAirbrakeNoticeBuilder Create();
IAirbrakeNoticeBuilder Create(AirbrakeConfiguration configuration);
}
如果你正在处理自定义的Airbreak结构,你将会有更多的工作。
E.g。对于AirbrakeNoticeBuilder
,您必须为您使用的任何相关类创建重复的POCO类型。
public interface IAirbrakeNoticeBuilder
{
AirbrakeNotice Notice(Exception exception);
}
由于您正在返回AirbrakeNotice
,您可能必须在Serialization文件夹下提取几乎所有POCO,具体取决于您使用了多少,以及您将多少传递回框架。
如果你决定复制POCO代码,包括整个对象树,你可以look into using AutoMapper to convert to and from your POCO copies。
或者,如果你不使用你要回来的类中的值,并将它们传回SharpBreak代码,你可以想出一些不透明的参考方案,它将使用你的字典不透明的引用类型为实际的POCO类型。然后,您不必将整个POCO对象树复制到代码中,也不需要花费太多的运行时开销来来回映射对象树:
public class AirbrakeNotice
{
// Note there is no implementation
}
internal class AirbreakNoticeMap
{
static AirbreakNoticeMap()
{
Map = new Dictionary<AirbreakNotice, SharpBreak.AirbreakNotice>();
}
public static Dictionary<AirbreakNotice, SharpBreak.AirbreakNotice> Map { get; }
}
public interface IAirbrakeClient
{
void Send(AirbrakeNotice notice);
// ...
}
internal class AirbrakeClientWrapper : IAirbrakeClient
{
private AirbrakeClient _airbrakeClient;
public void Send(AirbrakeNotice notice)
{
SharpBreak.AirbrakeNotice actualNotice = AirbreakNoticeMap.Map[notice];
_airbrakeClient.Send(actualNotice);
}
// ...
}
internal class AirbrakeNoticeBuilderWrapper : IAirbrakeNoticeBuilder
{
AirbrakeNoticeBuilder _airbrakeNoticeBuilder;
public AirbrakeNotice Notice(Exception exception)
{
SharpBreak.AirbrakeNotice actualNotice =
_airbrakeNoticeBuilder.Notice(exception);
AirbrakeNotice result = new AirbrakeNotice();
AirbreakNoticeMap.Map[result] = actualNotice;
return result;
}
// ...
}
请记住,您只需要包装要使用的公共接口的类和部分。即使您没有包装其整个公共接口,该对象在内部仍将表现相同。这可能意味着你必须做更少的工作,所以要认真思考,尽量只包装你现在需要的东西,以及你将来需要的东西。记住YAGNI。
答案 3 :(得分:0)
我真正喜欢这样的问题的编程风格是编写尽可能多的强类型代码,然后将逻辑从动态类型代码移交给强类型代码。所以我会写这样的代码:
//your code which gets types
Type clientType = exportedTypes.FirstOrDefault(type => type.Name == "AirbrakeClient");
Type noticeType = exportedTypes.FirstOrDefault(type => type.Name == "AirbrakeNotice");
//construct my helper object
var makeDelegateHelperType=typeof(MakeDelegateHelper<,>).MakeGenericType(clientType, noticeType);
var makeDelegateHelper=(MakeDelegateHelper)Activator.CreateInstance(makeDelegateHelperType);
//now I am in strongly-typed world again
var sendAction=makeDelegateHelper.MakeSendAction();
这是辅助对象的定义,它可以通过较少的反射调用来消除。
public abstract class MakeDelegateHelper {
public abstract Action<object> MakeSendAction();
}
public class MakeDelegateHelper<TClient,TNotice> : MakeDelegateHelper where TClient : new() {
public override Action<object> MakeSendAction() {
var sendMethod = typeof(TClient).GetMethod("Send", new[] { typeof(TNotice) });
var client=new TClient();
var action=(Action<TNotice>)Delegate.CreateDelegate(typeof(Action<TNotice>), client, sendMethod);
return o => action((TNotice)o);
}
}