«Объединение» типов без использования отражения или дублирования кода?

2

Кажется, не кажется хорошим заголовком, мои извинения. Не стесняйтесь менять его на что-то более подходящее; Я был бы признателен.

Я в необычной позиции здесь (или, может быть, не так необычно). У меня есть базовый тип, из которого будут разбиты многие типы, которые расширяют его функциональность, но остаются довольно последовательными. Некоторые из них будут более или менее дублировать, но представлять данные по-разному. Все они могут управляться одинаково, поэтому то, что я решил сделать, это создать класс "слияния", который займет два или более из этих объектов и позволит контролировать их все сразу, но я прибегал к используя отражение. Если бы я переопределял всех членов базового класса и просто перенаправлял все наборы /gets/etc, это просто неправильно, потому что если базовый класс для типов, которые я "сменил", должен был когда-либо меняться, значит, необходимо слияние класс.

Но это приводит к затратам на производительность, которые, как мне кажется, можно избежать. Спектакль поражал не только от отражения, но и от предсказуемого бокса/распаковки во время отражения.

Вот пример псевдокода того, что я делаю:

class SomeBase
{
    public virtual bool SomeBool { get; set; }
}

class SomeDerived : SomeBase
{
    // ... extends SomeBase
}

class SomeMerger
{
    private SomeBase[] collection;

    public SomeMerger(SomeBase[] collection)
    {
        this.collection = collection;
    }

    public void SetProperty(string propertyName, object value)
    {
        for (int i = 0; i < this.collection.Length; i++)
        {
            PropertyInfo pi = collection[i].GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
            if (pi != null)
                pi.SetValue(collection[i], value, null);
        }
    }

    // .. etc
}

Теперь то, что я хотел бы сделать, - это доступ к элементам доступа, как если бы они были единым объектом внутри класса слияния (например, "SomeMergerObject.SomeBool = true" установил бы все свойства SomeBool во всех объектах, которые он сливается в true, делая синтаксис более естественным). Тем не менее, единственный способ, которым я могу это сделать, - переопределить все методы и свойства базового класса и перенаправить их на вызовы (что, я думаю, не будет считаться правильным). Есть ли более чистый/лучший способ достичь этого?

Извините, если я плохо справился с этим объяснением. Yell, если вы смущены, и я попытаюсь уточнить.:)

Edit:

Думаю, мне нужно немного разъяснить. Я полагаю, что я вводил в заблуждение, когда говорил: "У меня этот базовый тип и т.д." - реализация в том виде, в каком она есть, и не моя, все, что я пытаюсь сделать, это упростить работу. Вместо того, чтобы устанавливать базовые свойства для нескольких объектов, которые все разделяют в таких областях, как состояние видимости (в качестве примера), я думал, что это будет приятная функция (хотя тривиальная и большая работа, чем это стоит... но ради любопытства, почему бы не исследовать идею?), чтобы коллекция объектов вела себя как одна. Это даже не приближается к проблеме, а просто идея для улучшения, с которой я чувствовал себя флиртовать.

Я не предлагал "новую функцию языка", и тон моего вопроса заключался в том, что есть способ сделать это, чистым и правильным способом. Наверное, я представил свой запрос плохо, извините за это.

  • 3
    Похоже, вы создаете проблему. Может быть, вы можете опубликовать более подробную информацию о вашей ситуации - безусловно, есть лучший дизайн для того, чего вы пытаетесь достичь.
Теги:

4 ответа

1
Лучший ответ

Если я правильно интерпретирую вопрос, а затем код здесь:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
public interface ISomeInterface {
    void SetSomeBool(bool value);
}
class SomeBase : ISomeInterface {
    public virtual bool SomeBool { get; set; }
    void ISomeInterface.SetSomeBool(bool value) { SomeBool = value; }
}
class SomeDerived : SomeBase {
    // ... extends SomeBase
}
static class Program {
    static void Main() {
        var item1 = new SomeBase();
        var item2 = new SomeDerived();
        var items = new List<ISomeInterface> { item1, item2};
        ISomeInterface group = GroupGenerator.Create(items);
        group.SetSomeBool(true);
        Console.WriteLine(item1.SomeBool); // true
        Console.WriteLine(item2.SomeBool); // true
        group.SetSomeBool(false);
        Console.WriteLine(item1.SomeBool); // false
        Console.WriteLine(item2.SomeBool); // false
    }
}

Обратите внимание, что он также будет работать со свойствами, но get должен будет генерировать исключение (set применяется ко всем). По этой причине я предпочитаю метод явного метода (нет get на интерфейсе). Это также может быть свойство set -only, но они действительно редко:

public interface ISomeInterface
{
    bool SomeBool { set; }
}
class SomeBase : ISomeInterface
{
    public virtual bool SomeBool { get; set; }
}
class SomeDerived : SomeBase
{
    // ... extends SomeBase
}
static class Program
{
    static void Main()
    {
        var item1 = new SomeBase();
        var item2 = new SomeDerived();
        var items = new List<ISomeInterface> { item1, item2};
        ISomeInterface group = GroupGenerator.Create(items);
        group.SomeBool = true;
        Console.WriteLine(item1.SomeBool); // true
        Console.WriteLine(item2.SomeBool); // true
        group.SomeBool = false;
        Console.WriteLine(item1.SomeBool); // false
        Console.WriteLine(item2.SomeBool); // false
    }
}
  • 0
    Я писал что-то похожее (но менее мощное), но потом понял, что моя идея (как эта) по-прежнему не учитывает желание передавать имя свойства, которое должно быть установлено, в виде строки . Здесь вам все еще нужно явно перечислить в ISomeInterface все свойства, которые вы, возможно, захотите установить. Я думаю, что нет никакого способа избежать отражения / кода котельной пластины, если это жесткое требование.
  • 0
    Моя интерпретация заключается в том, что этот подход (со строкой) является просто иллюстрацией того, как он работает сейчас, через рефлексию.
Показать ещё 2 комментария
4

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

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

  • 0
    Причина, по которой я использовал рефлексию, состояла в том, чтобы избежать этого "кошмара обслуживания". Если вы имеете в виду конечный бит (переопределение всех членов), именно поэтому я избегаю его (и не буду его реализовывать). И у меня на самом деле нет проблемы, я просто спрашиваю, возможно ли то, что я хотел бы сделать, таким образом, чтобы позже, как вы сказали, не стало бы катастрофой. :П
  • 1
    Кошмар обслуживания, о котором я говорю, - это использование вами относительно сложной функции (Reflection) для решения относительно простой проблемы. Тот, кто придет позже, должен будет понять и поддерживать ваш код Reflection.
Показать ещё 1 комментарий
0

Этот код использует ConcurrentDictionary<TKey, TValue> из .NET 4, но вы можете написать его с помощью Dictionary<TKey, TValue> и либо сделать его не потоковым, безопасно или использовать грубую блокировку (оператор lock) и принести жертву немного производительности.

public static class DynamicObjects
{
    private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, Action<object, object>>> _setters
        = new ConcurrentDictionary<Type, ConcurrentDictionary<string, Action<object, object>>>();

    public static void SetProperty(object instance, string property, object value)
    {
        if (instance == null)
            throw new ArgumentNullException("instance");
        if (property == null)
            throw new ArgumentNullException("property");
        if (property.Length == 0)
            throw new ArgumentException("The property name cannot be empty.", "property");

        Type type = instance.GetType();
        var settersForType = _setters.GetOrAdd(type, CreateDictionaryForType);
        var setter = settersForType.GetOrAdd(property, (obj, newValue) => CreateSetterForProperty(type, property));
        setter(instance, value);
    }

    private static ConcurrentDictionary<string, Action<object, object>> CreateDictionaryForType(Type type)
    {
        return new ConcurrentDictionary<string, Action<object, object>>();
    }

    private static Action<object, object> CreateSetterForProperty(Type type, string property)
    {
        var propertyInfo = type.GetProperty(property);
        if (propertyInfo == null)
            return (o, v) => ThrowInvalidPropertyException(type, property);

        var setterMethod = propertyInfo.GetSetMethod();
        if (setterMethod == null)
            return (o, v) => ThrowReadOnlyPropertyException(type, property);

        ParameterExpression instance = Expression.Parameter(typeof(object), "instance");
        ParameterExpression value = Expression.Parameter(typeof(object), "value");
        Expression<Action<object, object>> expression =
            Expression.Lambda<Action<object, object>>(
            Expression.Call(instance, setterMethod, Expression.Convert(value, propertyInfo.PropertyType)),
            instance,
            value);

        return expression.Compile();
    }

    private static void ThrowInvalidPropertyException(Type type, string propertyName)
    {
        throw new InvalidOperationException("The type '" + type.FullName + "' does not have a publicly accessible property '" + propertyName + "'.");
    }

    private static void ThrowReadOnlyPropertyException(Type type, string propertyName)
    {
        throw new InvalidOperationException("The type '" + type.FullName + "' does not have a publicly visible setter for the '" + propertyName + "' property.");
    }
}
0

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

Однако вы можете использовать новое ключевое слово динамическое в .net 4.0 и получить свой класс для реализации IDynamicMetaObjectProvider отложить проверку сайта до времени выполнения.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню