Как использовать отражение для упрощения конструкторов и сравнений?
Мне не нравится иметь кучу методов "влево/вправо". Каждый раз, когда свойство добавляется или удаляется, я должен исправить каждый метод. И сам код просто выглядит... неправильным.
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);
}
}
Я могу просто попытаться заставить парадигму, которую я узнал от 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, используя другой набор свойств в другом порядке). Хуже того, это потеря сильной типизации. Это действительно начинает пахнуть.
Ну а как насчет использования атрибутов для каждого свойства для определения порядка? Я начал по этой дороге, и действительно, она работала хорошо, но это просто заставило все выглядело раздутым. Он отлично работает семантически, но я всегда опасаюсь использовать расширенные функции только потому, что они "опрятные". Использует ли рефлексия таким образом избыток? Есть ли еще какое-то другое решение проблемы с левым/правым кодом, которую я пропускаю?
Ответы
Ответ 1
Использование рефлексии само по себе неплохо, но вы получите удар производительности, особенно если вы это сделаете рекурсивно.
Я не поклонник конструкторов с жесткой кодировкой, потому что разработчики забывают обновлять их, добавляя новые свойства в класс.
Существуют и другие способы достижения того, что вы хотите, в том числе Marc Gravells "Дескриптор свойств Hyper" , или если вы хотите узнать некоторые IL и OPCodes, вы может использовать System.Reflection.Emit или даже Cecil from Mono.
Вот пример использования дескриптора Hyper Property Descriptor, который вы можете настроить для своих нужд:
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);
}
Ответ 2
Я знаю, что уже есть ответ на этот вопрос, но я хотел бы указать, что есть библиотека, которая объединяет несколько стратегий смягчения влияния производительности, о которых говорили несколько человек.
Библиотека называется AutoMapper и отображает ее из одного объекта в другой и делает это путем динамического создания сборки IL на лету, Это гарантирует, что, кроме первого удара, вы получите превосходную производительность, и ваш код будет намного проще:
public Foo(Foo other)
{
Mapper.Map(other, this);
}
Это имеет тенденцию работать отлично и имеет дополнительный бонус, который не придумывается здесь, и я поклонник.
Я провел некоторое тестирование производительности, и после первого попадания 20 мс (все еще довольно быстро) он приближался к 0, как вы можете получить. Довольно впечатляет.
Надеюсь, это поможет кому-то.
Ответ 3
IMHO, отражение - очень мощная функция С#, но которая, скорее всего, приведет к раздутому коду и значительно добавит к кривой обучения кода и уменьшит ремонтопригодность. Вероятнее всего, вы будете совершать ошибки (когда базовый рефакторинг может привести к ошибкам) и больше боится изменить имя любого свойства (если вам посчастливилось найти лучшее имя) или тому подобное.
У меня лично есть код с аналогичной проблемой, и у меня была та же идея добавления атрибутов для поддержания порядка и т.д. Но моя команда (включая меня) считала, что лучше потерять некоторое время, изменив дизайн, чтобы это не нужно, Возможно, эта проблема вызвана плохим дизайном (ну, это было в моем случае, но я не могу сказать то же самое о вашем).
Ответ 4
Основная проблема заключается в том, что вы пытаетесь использовать статически типизированный язык, например, динамически типизированный.
На самом деле нет необходимости в каких-либо фантазии. Если вы хотите иметь возможность итерировать свойства, вы можете использовать Map < > в качестве хранилища резервных копий для всех свойств вашего класса.
Кстати, именно так мастер проекта VS подразумевает настройки приложения для вас. (см. System.Configuration.ApplicationSettingsBase). Он также очень похож на lua-like
public bool ConfirmSync {
get {
return ((bool)(this["ConfirmSync"]));
}
set {
this["ConfirmSync"] = value;
}
}