暴露操作作为 Action
发布于 2024-12-07 03:27:30 字数 2016 浏览 0 评论 0 原文

我正在创建一个框架,其中包含一个库(特别是 SharpBrake)的包装器,该包装器执行与 SharpBrake 的所有交互通过反射,所以我的框架的第三方不存在对库的硬依赖。

如果我的框架的第 3 方想要使用 SharpBrake,他们可以将 SharpBrake.dll 放入 bin 文件夹中,但如果他们不这样做,他们就可以忘记它。如果我的框架显式引用 SharpBrake 类型,则我的框架的用户将在运行时缺少 SharpBrake.dll 时遇到异常,这是我不希望的。

因此,我的包装器首先从磁盘加载 SharpBrake.dll,找到 AirbrakeClient 类型,并将指向 AirbrakeClient.Send(AirbrakeNotice) 方法的委托存储在私有字段中。然而,我的问题是,由于 Send() 方法采用 AirbrakeNotice 对象,并且我无法直接引用 AirbrakeNotice 对象,所以我需要以某种方式将 Send() 方法转换为 Action

我强烈感觉这是不可能的,但我想在决定公开 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]”。

显然,因为 sendMethodDelegateAction 而不是 Action。由于我无法在代码中提及 AirbrakeNotice,因此我被迫这样做:

Action<object> sendAction = x => sendMethodDelegate.DynamicInvoke(x);

或者直接公开 Delegate sendMethodDelegate 。这可能吗?我知道有可能会遇到 object 的类型与 AirbrakeNotice 不同的情况,这会很糟糕,但无论如何,看看反射会弄乱多少,我希望某处有漏洞。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

心意如水 2024-12-14 03:27:31

对于此类问题,我真正喜欢的编程风格是编写尽可能多的强类型代码,然后将逻辑从动态类型代码移交给强类型代码。所以我会这样编写代码:

  //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);
  }
}
怪我太投入 2024-12-14 03:27:30

如果您乐意使用表达式树,那么这相当简单:

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();

认为这就是您想要的......

If you're happy to use expression trees, it's reasonably simple:

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();

I think that's what you want...

一绘本一梦想 2024-12-14 03:27:30

如果您不需要 C# 4 以下的支持,您可以使用 dynamicDynamicInvoke 获得更高的性能。

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 skeets 用表达式树回答绝对是方法去。

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:

Type clientType = exportedTypes.FirstOrDefault(type => type.Name == "AirbrakeClient");
dynamic client = Activator.CreateInstance(clientType);

...
client.Send(anAirbrakeNotice);

But if you need to support .net 3.5 jon skeets answer with expression trees is definitely the way to go.

念﹏祤嫣 2024-12-14 03:27:30

来自我对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);
    }
}

这种方法的优点是您只使用一次反射(在加载时),并且永远不会使用反射再次触摸它。修改为使用依赖注入或模拟对象(用于测试)也很简单。

编辑:

对于其他类型,需要更多的工作。

您可能需要使用抽象工厂模式来实例化AirbrakeNoticeBuilder,因为您需要直接处理接口,并且不能将构造函数放入接口中。

public interface IAirbrakeNoticeBuilderFactory
{
    IAirbrakeNoticeBuilder Create();
    IAirbrakeNoticeBuilder Create(AirbrakeConfiguration configuration);
}

如果您正在处理自定义 Airbreak 结构,您将需要做更多的工作。

例如,对于 AirbrakeNoticeBuilder,您必须为您使用的任何相关类创建重复的 POCO 类型。

public interface IAirbrakeNoticeBuilder
{
    AirbrakeNotice Notice(Exception exception);
}

由于您要返回 AirbrakeNotice,因此您可能必须提取 Serialization 文件夹下的几乎所有 POCO,具体取决于您使用的数量以及传回框架的数量。

如果您决定复制 POCO 代码(包括整个对象树),您可以考虑使用 AutoMapper 在 POCO 副本之间进行转换< /a>.

或者,如果您不使用要返回的类中的值,而只是将它们传递回 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

From my comment on the OP:

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)
    {
    }
}

In a load-if-available (via reflections) assembly

public ExtensionsAdapter : IExtensions
{
    void SendToAirbrake(Exception exception)
    {
        SharpBrake.Extensions.SendToAirbrake(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.

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:

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文