我正在创建一个框架,其中包含一个库(特别是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.Action
1[SharpBrake.Serialization.AirbrakeNotice]' to type 'System.Action1System.Object'.:无法转换类型为System.InvalidCastException的对象
显然,因为sendMethodDelegate是Action<AirbrakeNotice>而不是Action<object>。因为我不能在代码中提到AirbrakeNotice,所以我不得不这样做:
Action<object> sendAction = x => sendMethodDelegate.DynamicInvoke(x);或者直接公开Delegate sendMethodDelegate。这个是可能的吗?我知道有可能陷入object可能与AirbrakeNotice不同类型的情况--这将是很糟糕的,但是看看你到底能在多大程度上破坏反射,我希望在某个地方有一个漏洞。
发布于 2011-09-30 13:29:24
如果您不需要低于C# 4的支持,那么使用dynamic和DynamicInvoke可以获得更好的性能。
Action<dynamic> sendAction = x => sendMethodDelegate(x);实际上,如果您可以使用dynamic,我想您甚至不需要上面的内容,因为如果您这样做了,它将提高性能并简化所有事情:
Type clientType = exportedTypes.FirstOrDefault(type => type.Name == "AirbrakeClient");
dynamic client = Activator.CreateInstance(clientType);
...
client.Send(anAirbrakeNotice);但是如果你需要支持.net 3.5乔恩,用表达式树回答绝对是可行的。
发布于 2011-09-30 11:31:11
如果您乐于使用表达式树,那么它相当简单:
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();我想这就是你想要的..。
发布于 2011-09-30 20:31:42
根据我对“任择议定书”的评论:
如果您关心性能,我将避免过多地使用反射。如果您可以为所使用的类(Es)提供一个接口,那么我将创建一个接口。然后编写一个通过调用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);
}
}这种方法的优点是,您只使用反射一次(在加载),并且再也不会碰它。修改以使用依赖项注入或模拟对象(用于测试)也很简单。
编辑:
对于其他类型,它将需要更多的工作。
您可能需要使用抽象工厂模式来实例化一个AirbrakeNoticeBuilder,因为您需要直接处理接口,并且不能将构造函数放在接口中。
public interface IAirbrakeNoticeBuilderFactory
{
IAirbrakeNoticeBuilder Create();
IAirbrakeNoticeBuilder Create(AirbrakeConfiguration configuration);
}如果你处理的是自定义的空气交换结构,你将有更多的工作。
例如,对于AirbrakeNoticeBuilder,您必须为您使用的任何相关类创建重复的POCO类型。
public interface IAirbrakeNoticeBuilder
{
AirbrakeNotice Notice(Exception exception);
}因为您要返回AirbrakeNotice,所以您可能必须在序列化文件夹下插入几乎每个POCO,这取决于您使用了多少,以及传递给框架的数量。
如果您决定复制POCO代码,包括整个对象树,则可以使用查看如何使用AutoMapper与POCO副本进行转换。。
或者,如果您不使用您要返回的类中的值,并将它们传递回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;
}
// ...
}请记住,您只需要包装您将要使用的公共接口的类和部分。即使您没有包装它的整个公共接口,该对象在内部的行为仍然相同。这可能意味着你必须少做一些工作,所以好好想想,试着把你现在需要的东西和你知道将来需要的东西都包起来。记住雅格尼。
https://stackoverflow.com/questions/7609629
复制相似问题