首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将Action<T>公开为Action<object>

将Action<T>公开为Action<object>
EN

Stack Overflow用户
提问于 2011-09-30 11:21:22
回答 4查看 3.1K关注 0票数 3

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

如果我的框架中的第三方想要使用SharpBrake,他们可以将SharpBrake.dll填充到bin文件夹中,但如果不这样做,他们就可以忘记它。如果我的框架对SharpBrake类型有明确的引用,我的框架的用户将在SharpBrake.dll缺失的运行时获得异常,这是我不想要的。

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

我有强烈的感觉,这是不可能的,但我想在决定公开Delegate和使用DynamicInvoke()之前探索所有选项,我认为这远远不是最优的、性能上的。我想做的是:

代码语言:javascript
复制
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.Action1[SharpBrake.Serialization.AirbrakeNotice]' to type 'System.Action1System.Object'.:无法转换类型为System.InvalidCastException的对象

显然,因为sendMethodDelegateAction<AirbrakeNotice>而不是Action<object>。因为我不能在代码中提到AirbrakeNotice,所以我不得不这样做:

代码语言:javascript
复制
Action<object> sendAction = x => sendMethodDelegate.DynamicInvoke(x);

或者直接公开Delegate sendMethodDelegate。这个是可能的吗?我知道有可能陷入object可能与AirbrakeNotice不同类型的情况--这将是很糟糕的,但是看看你到底能在多大程度上破坏反射,我希望在某个地方有一个漏洞。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2011-09-30 13:29:24

如果您不需要低于C# 4的支持,那么使用dynamicDynamicInvoke可以获得更好的性能。

代码语言:javascript
复制
Action<dynamic> sendAction = x => sendMethodDelegate(x);

实际上,如果您可以使用dynamic,我想您甚至不需要上面的内容,因为如果您这样做了,它将提高性能并简化所有事情:

代码语言:javascript
复制
Type clientType = exportedTypes.FirstOrDefault(type => type.Name == "AirbrakeClient");
dynamic client = Activator.CreateInstance(clientType);

...
client.Send(anAirbrakeNotice);

但是如果你需要支持.net 3.5乔恩,用表达式树回答绝对是可行的。

票数 2
EN

Stack Overflow用户

发布于 2011-09-30 11:31:11

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

代码语言:javascript
复制
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();

我想这就是你想要的..。

票数 6
EN

Stack Overflow用户

发布于 2011-09-30 20:31:42

根据我对“任择议定书”的评论:

如果您关心性能,我将避免过多地使用反射。如果您可以为所使用的类(Es)提供一个接口,那么我将创建一个接口。然后编写一个通过调用SharpBreak代码实现接口的包装器,并将其填充到一个单独的DLL中。然后只动态加载包装程序集和具体包装类型,并调用该接口。然后,您就不必在方法级别上进行反射。

我不确定您需要的所有类,但下面是一个简单的示例,说明如何通过基于接口的松耦合连接到库中。

在程序的程序集中:

代码语言:javascript
复制
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)
    {
    }
}

在负载如果可用(通过反射)程序集中。

代码语言:javascript
复制
public ExtensionsAdapter : IExtensions
{
    void SendToAirbrake(Exception exception)
    {
        SharpBrake.Extensions.SendToAirbrake(exception);
    }
}

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

编辑:

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

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

代码语言:javascript
复制
public interface IAirbrakeNoticeBuilderFactory
{
    IAirbrakeNoticeBuilder Create();
    IAirbrakeNoticeBuilder Create(AirbrakeConfiguration configuration);
}

如果你处理的是自定义的空气交换结构,你将有更多的工作。

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

代码语言:javascript
复制
public interface IAirbrakeNoticeBuilder
{
    AirbrakeNotice Notice(Exception exception);
}

因为您要返回AirbrakeNotice,所以您可能必须在序列化文件夹下插入几乎每个POCO,这取决于您使用了多少,以及传递给框架的数量。

如果您决定复制POCO代码,包括整个对象树,则可以使用查看如何使用AutoMapper与POCO副本进行转换。

或者,如果您不使用您要返回的类中的值,并将它们传递回SharpBreak代码,您可以想出某种不透明的引用方案,将您的不透明引用类型的字典使用到实际的POCO类型。然后,您不必将整个POCO对象树复制到代码中,也不需要花费那么多的运行时开销来来回映射对象树:

代码语言:javascript
复制
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;
    }

    // ...
}

请记住,您只需要包装您将要使用的公共接口的类和部分。即使您没有包装它的整个公共接口,该对象在内部的行为仍然相同。这可能意味着你必须少做一些工作,所以好好想想,试着把你现在需要的东西和你知道将来需要的东西都包起来。记住雅格尼

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7609629

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档