Как использовать отражение, чтобы упростить конструкторы и сравнения? - PullRequest
12 голосов
/ 27 августа 2009

Я ненавижу иметь кучу "левых / правых" методов. Каждый раз, когда свойство добавляется или удаляется, я должен исправить каждый метод. И сам код выглядит просто ... неправильно.

public Foo(Foo other)
{
    this.Bar = other.Bar;
    this.Baz = other.Baz;
    this.Lur = other.Lur;
    this.Qux = other.Qux;
    this.Xyzzy= other.Xyzzy;
}

На самом деле это просто развернутый цикл, который перебирает свойства, копируя их между объектами. Так почему бы не быть честным по этому поводу? Отражение на помощь!

public Foo(IFoo other)
{
    foreach (var property in typeof(IFoo).GetProperties())
    {
        property.SetValue(this, property.GetValue(other, null), null);
    }
}

Возможно, я просто пытаюсь навязать C # парадигму, которую я выучил от Lua, но этот конкретный пример не кажется мне слишком вонючим. Отсюда я начал делать более сложные вещи, которые были чувствительны к порядку полей. Например, вместо того, чтобы иметь стек практически идентичных if операторов для составления строки из полей, я просто перебираю их в нужном порядке:

public override string ToString()
{
    var toJoin = new List<string>();
    foreach (var property in tostringFields)
    {
        object value = property.GetValue(this, null);
        if (value != null)
            toJoin.Add(value.ToString());
    }
    return string.Join(" ", toJoin.ToArray());
}
private static readonly PropertyInfo[] tostringFields =
{
    typeof(IFoo).GetProperty("Bar"),
    typeof(IFoo).GetProperty("Baz"),
    typeof(IFoo).GetProperty("Lur"),
    typeof(IFoo).GetProperty("Qux"),
    typeof(IFoo).GetProperty("Xyzzy"),
};

Так что теперь у меня есть итерация, которую я хотел, но у меня все еще есть стеки кода, отражающие каждое свойство, которое меня интересует (я также делаю это для CompareTo, используя другой набор свойств в другом порядке). Хуже, чем потеря строгой типизации. Это действительно начинает пахнуть.

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

Ответы [ 4 ]

5 голосов
/ 27 августа 2009

Использование отражения само по себе неплохо, но вы получите удар по производительности, особенно если вы сделаете это рекурсивно.

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

Существуют и другие способы достижения желаемого, в том числе Марк Гравеллс Дескриптор гиперпространства или, если вы хотите узнать некоторые IL и OPCodes, вы можете использовать System.Reflection.Emit или даже Cecil из Mono .

Вот пример использования дескриптора гиперпространства, который вы можете адаптировать к вашим потребностям:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using Hyper.ComponentModel;
namespace Test {
    class Person {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    class Program {
        static void Main() {
            HyperTypeDescriptionProvider.Add(typeof(Person));
            var properties = new Dictionary<string, object> { { "Id", 10 }, { "Name", "Fred Flintstone" } };
            Person person = new Person();
            DynamicUpdate(person, properties);
            Console.WriteLine("Id: {0}; Name: {1}", person.Id, person.Name);
            Console.ReadKey();
        }
        public static void DynamicUpdate<T>(T entity, Dictionary<string, object>  {
            foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(typeof(T)))
                if (properties.ContainsKey(propertyDescriptor.Name))
                    propertyDescriptor.SetValue(entity, properties[propertyDescriptor.Name]);
        }
    }
}

Если вы решите продолжать использовать рефлексию, вы можете снизить производительность, кэшируя ваши вызовы GetProperties (), например так:

public Foo(IFoo other) {
    foreach (var property in MyCacheProvider.GetProperties<IFoo>())
        property.SetValue(this, property.GetValue(other, null), null);
}
3 голосов
/ 01 сентября 2009

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

Библиотека называется AutoMapper , и она отображается от одного объекта к другому и делает это путем динамического создания сборки IL на лету. Это гарантирует, что, кроме первого раза, вы получите превосходную производительность и ваш код будет намного проще:

public Foo(Foo other)
{
    Mapper.Map(other, this);
}

Это имеет тенденцию работать отлично и имеет дополнительный бонус - не изобретаться здесь, и я его фанат.

Я провел некоторое тестирование производительности, и после первого удара в 20 мс (все еще довольно быстро) он был как можно ближе к 0, насколько это возможно. Довольно впечатляет.

Надеюсь, это кому-нибудь поможет.

2 голосов
/ 02 сентября 2009

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

Нет ничего особенного. Если вы хотите иметь возможность перебирать свойства, вы можете использовать Map <> в качестве резервного хранилища для всех свойств вашего класса.

По совпадению, именно так мастер проекта VS влияет на настройки приложения. (см. System.Configuration.ApplicationSettingsBase) Он также очень похож на lua

   public bool ConfirmSync {
        get {
            return ((bool)(this["ConfirmSync"]));
        }
        set {
            this["ConfirmSync"] = value;
        }
    }
2 голосов
/ 27 августа 2009

ИМХО, отражение - очень мощная особенность C #, но очень вероятно, что оно приведет к раздутому коду, что значительно увеличивает кривую обучения кода и снижает удобство сопровождения. С большей вероятностью вы будете совершать ошибки (как только базовый рефакторинг может привести к ошибкам), и будете бояться менять имя какого-либо свойства (если вам удастся найти более подходящее имя) или тому подобное.

У меня лично есть код с похожей проблемой, и у меня была та же идея добавить атрибуты для поддержания порядка и т. Д. Но моя команда (включая меня) думала, что было бы лучше потерять некоторое время, изменяя дизайн, чтобы не нуждаться в этом , Возможно, эта проблема вызвана плохим дизайном (ну, это было в моем случае, но я не могу сказать то же самое о вашем).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...