Сделать объект динамически реализовать интерфейс в коде - PullRequest
7 голосов
/ 17 августа 2010

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

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 ]

3 голосов
/ 17 августа 2010

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

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

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

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

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

3 голосов
/ 17 августа 2010

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

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;
    }
}
1 голос
/ 17 августа 2010

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

То, что вы ищете, почти достижимо с помощью 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!");
}
1 голос
/ 17 августа 2010

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

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

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

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

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

Недостатки:

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

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

1 голос
/ 17 августа 2010

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

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

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

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

...