Выставить действие <T>как действие <object> - PullRequest
3 голосов
/ 30 сентября 2011

Я создаю фреймворк, который содержит обертку вокруг библиотеки (в частности, SharpBrake ), которая выполняет все взаимодействие с SharpBrake с помощью отражения, поэтому нет жесткой зависимости от библиотеки для сторонних разработчиков моего фреймворка. 1003 *

Если сторонние разработчики моей платформы захотят использовать 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]'.

Очевидно, потому что sendMethodDelegate - это Action<AirbrakeNotice>, а не Action<object>. Поскольку я не могу упомянуть AirbrakeNotice в своем коде, я вынужден сделать это:

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

или просто выставив Delegate sendMethodDelegate напрямую. Это возможно? Я знаю, что есть шанс попасть в ситуации, когда object может быть другого типа, чем AirbrakeNotice, что было бы плохо, но, смотря на то, как много вы можете испортить отражением, я надеюсь, что где-то есть лазейка.

Ответы [ 4 ]

6 голосов
/ 30 сентября 2011

Если вы счастливы использовать деревья выражений, это достаточно просто:

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

Я думаю это то, что вы хотите ...

2 голосов
/ 30 сентября 2011

Если вам не нужна поддержка ниже C # 4, вы можете получить гораздо большую производительность, используя dynamic против 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, jonkeets ответ с деревьями выражений, безусловно, путь.

1 голос
/ 01 октября 2011

Из моего комментария к ОП:

Я бы избегал расширенного использования отражений, если вы беспокоитесь о производительности.Если вы можете придумать интерфейс для класса (ов), который вы используете, то я бы создал его.Затем напишите оболочку, которая реализует интерфейс, вызвав код 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);
    }
}

Преимущество этого подхода состоит в том, что вы используете отражения только один раз (под нагрузкой) и никогда не трогаете их снова.Также легко изменить, чтобы использовать внедрение зависимостей или фиктивные объекты (для тестирования).

Редактировать:

Для других типов это займет немного больше работы.

Возможно, вам потребуется использовать шаблон Abstract Factory для создания экземпляра AirbrakeNoticeBuilder, поскольку вам нужно иметь дело непосредственно с интерфейсом и не можете помещать конструкторы в интерфейсы.

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

Если вы имеете дело с пользовательскими структурами Airbreak, у вас будет еще больше работы.

Например, для AirbrakeNoticeBuilder вам придется создавать дубликаты типов POCO для любых связанных классов, которые выuse.

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

Поскольку вы возвращаете AirbrakeNotice, вам, возможно, придется загружать почти все POCO в папке Serialization, в зависимости от того, сколько вы используете, и сколько вы передаете обратно в платформу.

Если вы решите скопировать код 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;
    }

    // ...
}

Имейте в видучто вам нужно только обернуть классы и части открытого интерфейса, который вы собираетесь использовать.Объект все равно будет вести себя так же внутри, даже если вы не закроете весь его открытый интерфейс.Это может означать, что вам придется выполнять меньше работы, поэтому подумайте и постарайтесь обернуть только то, что вам нужно сейчас, и то, что, как вы знаете, вам понадобится в будущем.Помните ЯГНИ .

0 голосов
/ 30 сентября 2011

Стиль программирования, который мне очень понравился для подобных задач, - это написать как можно больше строго типизированного кода, а затем передать логику из динамически типизированного кода в строго типизированный код. Поэтому я написал бы ваш код так:

  //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);
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...