将Action <t>公开为Action <object> </object> </t>

时间:2011-09-30 11:21:23

标签: c# generics reflection delegates

我正在创建一个框架,其中包含一个围绕库(特别是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]'。

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

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

或直接公开Delegate sendMethodDelegate。这可能吗?我知道有可能遇到object可能与AirbrakeNotice不同类型的情况,这可能是坏的,但看看你有多少可以搞砸了反思,我希望有某处有漏洞。

4 个答案:

答案 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);
  }
}