Сделать объект динамически реализовать интерфейс в коде

Я хочу пройти этот тест - у кого-нибудь есть идеи, как это сделать?

public class Something
{
    public string Name {get; set}
}

public interface IWithId
{
    public Guid Id {get; set}
}

public class IdExtender 
{
    public static Object Extend(object toExtend)
    {
        ...?
    }
}

public class Tests 
{
    [Test]
    public void Should_extend_any_object()
    {
        var thing = new Something { Name = "Hello World!"};
        var extended = IdExtender.Extend(thing);
        Assert.IsTrue(extended is IWithId);
        Assert.IsTrue(extended.Id is Guid);
        Assert.IsTrue(extened.Name == "Hello World!");
    }
}

Я думаю, что-то подобное можно сделать с помощью динамического прокси-замка, linfu и т. Д., Но как?

5 ответов

Решение

Использование Castle DP (очевидно)

Что ж, вам придется создать новый объект для возврата, так как вы не можете получить существующий тип, получив новый интерфейс во время выполнения вашей программы.

Для этого вам нужно создать прокси, а затем скопировать состояние вашего ранее существующего объекта на прокси. DP не делает этого OOTB. В версии 2.5 вы могли использовать прокси нового класса с target, но это работало бы, только если все свойства этого типа были виртуальными.

Тем не мение. Вы можете сделать новый тип получить IWithId интерфейс либо путем смешивания в прокси с существующим объектом, который реализует свойство. Затем вызовы членов интерфейса будут перенаправлены на объект.

В качестве альтернативы вы можете предоставить его в качестве дополнительного интерфейса для реализации и иметь роль перехватчика в роли разработчика.

Сейчас я собираюсь с Линфу, как это:

public class IdExtender 
{
    public static Object Extend(object toExtend)
    {
        var dyn = new DynamicObject(toExtend);
        dyn.MixWith(new WithId {
                                Id = Guid.New()
                               });
        var extended = dyn.CreateDuck<IWithId>(returnValue.GetType().GetInterfaces());
        return extended;
    }
}

Если оставить в стороне вопрос о том, как динамически присоединять свойство или интерфейс, похоже, что вы пытаетесь дополнить существующие классы дополнительными данными. Очень типичным решением этой проблемы является использование Dictionary<Something, SomethingExtra> и сохраните его в некотором классе обслуживания, который поддерживает отображение. Теперь, когда вам нужен доступ к SomethingExtra, вы просто запрашиваете у класса обслуживания соответствующую информацию.

Преимущества:

  1. Реализацию легче понять и поддерживать, чем решение, использующее рефлексию и динамическое создание прокси.

  2. Если вам нужно извлечь из расширяемого типа, класс не может быть запечатан. Связывание информации внешне прекрасно работает с закрытыми классами.

  3. Вы можете связать информацию без необходимости отвечать за конструкцию дополненного объекта. Вы можете взять экземпляры, созданные где угодно, и связать новую информацию.

Недостатки:

  1. Вам нужно внедрить экземпляр службы, который поддерживает сопоставление. Вы можете сделать это через IoC-фреймворк, если вы используете его, ручное внедрение (обычно пропускаемое через конструктор) или, если нет другой альтернативы, через статический метод доступа Singleton.

  2. Если у вас очень большое количество экземпляров, и они быстро создаются / удаляются, накладные расходы на Словарь могут стать заметными. Я подозреваю, что вам потребуется очень большая нагрузка, прежде чем издержки станут заметными по сравнению с прокси-реализацией.

Я на самом деле ответил на аналогичный вопрос на прошлой неделе. Я также написал небольшую библиотеку, которая делает это одно. Это доступно из моего блога, который я беззастенчиво подключаю.

То, что вам нужно, почти достижимо с помощью Castle Dynamic Proxy. Единственное ограничение заключается в том, что существующий экземпляр должен реализовывать интерфейс, и все интересующие вас свойства / методы доступны через этот интерфейс.

public static TIntf CreateMixinWithTarget<TIntf>(TIntf target, params object[] instances) where TIntf : class{

    ProxyGenerator generator = new ProxyGenerator();
    ProxyGenerationOptions options = new ProxyGenerationOptions();

    instances.ToList().ForEach(obj => options.AddMixinInstance(obj));

    return generator.CreateInterfaceProxyWithTarget <TIntf>(target, options);
}

[Test]
public void Should_extend_any_object()
{
    var thing = new Something { Name = "Hello World!"};
    var extended = CreateMixinWithTarget<ISomething>(thing, new WithId(), new GuidImpl());
    Assert.IsTrue(extended is IWithId);
    Assert.IsTrue(extended.Id is Guid);
    Assert.IsTrue(extened.Name == "Hello World!");
}

Почему бы не использовать насмешливый фреймворк, такой как Moq или RhinoMocks. Они позволяют динамически реализовывать интерфейсы без необходимости создавать прокси напрямую.

С Moq вы можете написать:

var mock = new Mock<IWithId>();  // mock the interface
mock.Setup(foo => foo.Name).Returns("Hello World"); // setup a property

Кроме того, вам придется проделать сложную работу, чтобы получить то, что вы хотите. Насколько я знаю, вы не можете добавить метод или свойства в существующий класс во время выполнения. Вы можете вернуть экземпляр нового объекта, который динамически наследует от переданного типа. Castle определенно позволяет это, но необходимый код не особенно элегантен и не прост.

Другие вопросы по тегам