I'm creating a framework that contains a wrapper around a library (specifically SharpBrake) that performs all interaction with SharpBrake via reflection so there's no hard dependency on the library to 3rd parties of my framework.
If 3rd parties of my framework wants to use SharpBrake, they can just stuff the SharpBrake.dll into the bin folder, but if they don't, they can just forget about it. If my framework had explicit references to SharpBrake types, users of my framework would get exceptions during runtime of SharpBrake.dll missing, which I don't want.
So, my wrapper first loads SharpBrake.dll from disk, finds the AirbrakeClient type, and stores a delegate pointing to the AirbrakeClient.Send(AirbrakeNotice) method in a private field. My problem, however, is that since the Send() method takes an AirbrakeNotice object and I can't reference the AirbrakeNotice object directly, I need to somehow convert the Send() method to an Action<object>.
I have a strong feeling this isn't possible, but I want to explore all options before settling on exposing Delegate and using DynamicInvoke(), which I assume is far from optimal, performance-wise. What I would love to do is the following:
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;
However, this fails with the following exception:
System.InvalidCastException: Unable to cast object of type 'System.Action`1[SharpBrake.Serialization.AirbrakeNotice]' to type 'System.Action`1[System.Object]'.
Obviously, because sendMethodDelegate is an Action<AirbrakeNotice> and not an Action<object>. Since I can't mention AirbrakeNotice in my code, I'm forced to do this:
Action<object> sendAction = x => sendMethodDelegate.DynamicInvoke(x);
or just exposing the Delegate sendMethodDelegate directly. Is this possible? I know that there's chance of getting into situations where the object can be of a different type than AirbrakeNotice which would be bad, but seeing how much you can mess up with reflection anyway, I'm hoping there's a loophole somewhere.
//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);
}
}
The programming style I have come to really like for problems like this is to write as much strongly-typed code as possible, and then hand off the logic from the dynamically-typed code to the strongly-typed code. So I would write your code like this:
//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();
And this is the definition of the helper object, which is able to get away with fewer reflectiony calls.
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);
}
}
If you don't need below C# 4 support you can get much greater performance using the dynamic vs DynamicInvoke.
Action<dynamic> sendAction = x => sendMethodDelegate(x);
Actually I guess you wouldn't even need the above if you can use dynamic, because it would increase performance and simplify everything if you just did:
I'd avoid extended use of reflections if you are concerned about performance. If you can come up with an interface for the class(es) you are using, then I'd create one. Then write a wrapper that implements the interface by calling into the SharpBreak code, and stuff it in a separate DLL. Then dynamically load just your wrapper assembly and concrete wrapper type(s), and call into that interface. Then you don't have to do reflections at a method level.
I'm not sure all the classes you'd need, but here's a simple example of how you can hook into that library with loose coupling based on interfaces.
In your program's assembly:
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)
{
}
}
The advantage of this approach is that you only use reflections once (on load), and never touch it again. It is also simple to modify to use dependency injection, or mock objects (for testing).
Edit:
For other types it will take a bit more work.
You might need to use the Abstract Factory pattern to instantiate an AirbrakeNoticeBuilder, since you need to deal directly with the interface, and can't put constructors in interfaces.
public interface IAirbrakeNoticeBuilderFactory
{
IAirbrakeNoticeBuilder Create();
IAirbrakeNoticeBuilder Create(AirbrakeConfiguration configuration);
}
If you're dealing with custom Airbreak structures, you'll have even more work.
E.g. for the AirbrakeNoticeBuilder you will have to create duplicate POCO types for any related classes that you use.
public interface IAirbrakeNoticeBuilder
{
AirbrakeNotice Notice(Exception exception);
}
Since you're returning AirbrakeNotice, you might have to pull in nearly every POCO under the Serialization folder, depending on how much you use, and how much you pass back to the framework.
Alternately, if you don't use the values in the classes you're getting back, and just pass them back to the SharpBreak code, you could come up with some sort of opaque reference scheme that will use a dictionary of your opaque reference type to the actual POCO type. Then you don't have to copy the whole POCO object tree into your code, and you don't need to take as much runtime overhead to map the object trees back and forth:
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;
}
// ...
}
Keep in mind that you only need to wrap the classes and parts of the public interface that you're going to use. The object will still behave the same internally, even if you don't wrap its entire public interface. This might mean you have to do less work, so think hard and try to wrap only what you need now, and what you know you're going to need in the future. Keep YAGNI in mind.
发布评论
评论(4)
对于此类问题,我真正喜欢的编程风格是编写尽可能多的强类型代码,然后将逻辑从动态类型代码移交给强类型代码。所以我会这样编写代码:
这是辅助对象的定义,它能够减少反射调用。
The programming style I have come to really like for problems like this is to write as much strongly-typed code as possible, and then hand off the logic from the dynamically-typed code to the strongly-typed code. So I would write your code like this:
And this is the definition of the helper object, which is able to get away with fewer reflectiony calls.
如果您乐意使用表达式树,那么这相当简单:
我认为这就是您想要的......
If you're happy to use expression trees, it's reasonably simple:
I think that's what you want...
如果您不需要 C# 4 以下的支持,您可以使用
dynamic
与DynamicInvoke
获得更高的性能。实际上,我想如果您可以使用动态,您甚至不需要上述内容,因为如果您这样做,它会提高性能并简化一切:
但如果您需要支持 .net 3.5 jon skeets 用表达式树回答绝对是方法去。
If you don't need below C# 4 support you can get much greater performance using the
dynamic
vsDynamicInvoke
.Actually I guess you wouldn't even need the above if you can use dynamic, because it would increase performance and simplify everything if you just did:
But if you need to support .net 3.5 jon skeets answer with expression trees is definitely the way to go.
来自我对OP的评论:
我不确定您需要的所有类,但这里有一个简单的示例,说明如何通过基于接口的松散耦合来连接到该库。
在程序的程序集中:
在可用时加载(通过反射)程序集中
这种方法的优点是您只使用一次反射(在加载时),并且永远不会使用反射再次触摸它。修改为使用依赖注入或模拟对象(用于测试)也很简单。
编辑:
对于其他类型,需要更多的工作。
您可能需要使用抽象工厂模式来实例化
AirbrakeNoticeBuilder
,因为您需要直接处理接口,并且不能将构造函数放入接口中。如果您正在处理自定义 Airbreak 结构,您将需要做更多的工作。
例如,对于 AirbrakeNoticeBuilder,您必须为您使用的任何相关类创建重复的 POCO 类型。
由于您要返回
AirbrakeNotice
,因此您可能必须提取 Serialization 文件夹下的几乎所有 POCO,具体取决于您使用的数量以及传回框架的数量。如果您决定复制 POCO 代码(包括整个对象树),您可以考虑使用 AutoMapper 在 POCO 副本之间进行转换< /a>.
或者,如果您不使用要返回的类中的值,而只是将它们传递回 SharpBreak 代码,您可以提出某种不透明引用方案,该方案将使用不透明引用类型的字典到实际的 POCO 类型。然后,您不必将整个 POCO 对象树复制到代码中,也不需要花费太多的运行时开销来来回映射对象树:
请记住,您只需要包装类和您将要使用的公共界面的一部分。即使您没有包装其整个公共接口,该对象的内部行为仍然相同。这可能意味着您必须做更少的工作,因此请认真思考并尝试仅包含您现在需要的内容以及您知道将来需要的内容。请牢记YAGNI。
From my comment on the OP:
I'm not sure all the classes you'd need, but here's a simple example of how you can hook into that library with loose coupling based on interfaces.
In your program's assembly:
In a load-if-available (via reflections) assembly
The advantage of this approach is that you only use reflections once (on load), and never touch it again. It is also simple to modify to use dependency injection, or mock objects (for testing).
Edit:
For other types it will take a bit more work.
You might need to use the Abstract Factory pattern to instantiate an
AirbrakeNoticeBuilder
, since you need to deal directly with the interface, and can't put constructors in interfaces.If you're dealing with custom Airbreak structures, you'll have even more work.
E.g. for the
AirbrakeNoticeBuilder
you will have to create duplicate POCO types for any related classes that you use.Since you're returning
AirbrakeNotice
, you might have to pull in nearly every POCO under the Serialization folder, depending on how much you use, and how much you pass back to the framework.If you decide to copy the POCO code, including the whole object tree, you could look into using AutoMapper to convert to and from your POCO copies.
Alternately, if you don't use the values in the classes you're getting back, and just pass them back to the SharpBreak code, you could come up with some sort of opaque reference scheme that will use a dictionary of your opaque reference type to the actual POCO type. Then you don't have to copy the whole POCO object tree into your code, and you don't need to take as much runtime overhead to map the object trees back and forth:
Keep in mind that you only need to wrap the classes and parts of the public interface that you're going to use. The object will still behave the same internally, even if you don't wrap its entire public interface. This might mean you have to do less work, so think hard and try to wrap only what you need now, and what you know you're going to need in the future. Keep YAGNI in mind.