Использование анонимного типа в лямбде для отображения или частичного клонирования любого количества свойств объекта - PullRequest
1 голос
/ 28 марта 2012

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

Весь смысл здесь было использование, которое выглядит примерно так
(вдохновлено использованием GroupBy),

var botchedOrder = db.BuildingOrders.Find(id);
var redo = db.BuildingOrders.Create();
botchedOrder.MapTo(redo, x => new { x.BasePrice, x.FileAttachments, x.Notes });

Это у меня над головой, но я угадывал что-то вроде этого,

public static void MapTo<TObject, TProps>(
    this TObject source,
    TObject target,
    Action<TProps> properties) //?
{
    //copy the defined properties from source to target somehow?
    //originally I thought I could make an array of delegates..
}

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

РЕДАКТИРОВАТЬ: забыл указать void

Ответы [ 5 ]

4 голосов
/ 28 марта 2012

Я бы порекомендовал посмотреть AutoMapper , так как он решает те же проблемы.Кроме того, различные источники ORM (например, Massive, Petapoco и т. Д.) Имеют некоторые реализации (поскольку им необходимо отображать данные из БД в объект).

По сути, он работает либо с использованием отражения - для итерации по заданномуполя / свойства и установить их для другого объекта (также с помощью отражения).Существуют подходы к генерации динамических методов с использованием IL Generator или деревьев выражений, которые объединяют весь код отражения в один красивый и быстрый метод, сгенерированный и скомпилированный во время выполнения (таким образом, обеспечивающий повышение производительности).

Другой способ сделать это - использоватьдинамические или словари вместо анонимного класса - это устранит проблему «анонимности» и просто потребует привязать определенные ключи к свойствам - может быть снова выполнено с отражением (с использованием, например, [MappingAttribute ("Key")] для различия - атрибутвыдумка, просто чтобы дать общее представление).

1 голос
/ 28 марта 2012

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

public static void MapTo<TInput, TOutput>(this TInput input, TOutput output, Expression<Func<TInput, TOutput, bool>> expression)
        where TInput : class
        where TOutput : class
    {
        if (expression == null)
            throw new ArgumentNullException("expression");

        Stack<Expression> unhandeledExpressions = new Stack<Expression>();

        unhandeledExpressions.Push(expression.Body);

        while (unhandeledExpressions.Any())
        {
            Expression unhandeledExpression = unhandeledExpressions.Pop();

            switch (unhandeledExpression.NodeType)
            {
                case ExpressionType.AndAlso:
                    {
                        BinaryExpression binaryExpression = (BinaryExpression)unhandeledExpression;
                        unhandeledExpressions.Push(binaryExpression.Left);
                        unhandeledExpressions.Push(binaryExpression.Right);
                    }
                    break;
                case ExpressionType.Equal:
                    {
                        BinaryExpression binaryExpression = (BinaryExpression)unhandeledExpression;

                        MemberExpression leftArgumentExpression = binaryExpression.Left as MemberExpression;
                        MemberExpression rightArgumentExpression = binaryExpression.Right as MemberExpression;

                        if (leftArgumentExpression == null || rightArgumentExpression == null)
                            throw new InvalidOperationException("Can only map to member expressions");

                        output.GetType().GetProperty(leftArgumentExpression.Member.Name).SetValue(
                            output, input.GetType().GetProperty(rightArgumentExpression.Member.Name).GetValue(input, null), null);
                    }
                    break;
                default:
                    throw new InvalidOperationException("Expression type not supported");
            }
        }
    }
}

, который можно использовать следующим образом:

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

    public int Number { get; set; }
}

class DestinationType
{
    public string CustName { get; set; }

    public int Age { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var source = new SourceType()
        {
            Name = "Test",
            Number = 22
        };

        var destination = new DestinationType();

        source.MapTo(destination, (src, dst) => dst.CustName == src.Name && dst.Age == src.Number);

        bool assert = source.Name == destination.CustName && source.Number == destination.Age;
    }
}

Преимущество этого подхода состоит в том, что он позволяет вам определять свой собственный «язык» отображения, который вы можете сделать настолько сложным / расширенным, насколько вам нужно.

Тем не менее, я рекомендую вам использовать готовое решение, такое как AutoFaq или AutoMapper. Удачи

1 голос
/ 28 марта 2012

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

public static class ObjectExtensions
{
  public static void CopyFrom(this object Instance, object Source)
  {
    ObjectExtensions.CopyFrom(Instance, Source, false, null);
  }

  public static void CopyFrom(this object Instance, 
                              object Source, 
                              IEnumerable<string> IgnoreProperties)
  {
    ObjectExtensions.CopyFrom(Instance, Source, false, IgnoreProperties);
  }

  public static void CopyFrom(this object Instance, 
                              object Source, 
                              bool ThrowOnPropertyMismatch, 
                              IEnumerable<string> IgnoreProperties)
  {
    Type sourceType = Source.GetType();

    BindingFlags publicInstanceFlags = BindingFlags.Public 
                                       | BindingFlags.Instance;

    PropertyInfo[] sourceProperties = 
      sourceType.GetProperties(publicInstanceFlags);

    Type instanceType = Instance.GetType();

    foreach (PropertyInfo sourceProperty in sourceProperties)
    {
      if (IgnoreProperties == null
          || (IgnoreProperties.Count() > 0
              && !IgnoreProperties.Contains(sourceProperty.Name)))
     {
       PropertyInfo instanceProperty = 
         instanceType.GetProperty(sourceProperty.Name, publicInstanceFlags);

       if (instanceProperty != null
           && instanceProperty.PropertyType == sourceProperty.PropertyType
           && instanceProperty.GetSetMethod() != null
           && instanceProperty.GetSetMethod().IsPublic)
       {
         instanceProperty.SetValue(Instance, 
                                   sourceProperty.GetValue(Source, null), 
                                   null);
       }
       else 
       if (ThrowOnPropertyMismatch
           && instanceProperty.PropertyType != sourceProperty.PropertyType)
       {
         throw new InvalidCastException(
           string.Format("Unable to cast source {0}.{1} to destination {2}.{3}.",
                         Source.GetType().Name,
                         sourceProperty.Name,
                         Instance.GetType().Name,
                         instanceProperty.Name));
       }
     }
   }
}
1 голос
/ 28 марта 2012

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

public static void MapTo<TObject>(this TObject source, TObject target, params Action<TObject, TObject>[] properties)
{
    foreach (var property in properties)
    {
        property(source, target);
    }
}

Называется как:

void Copy(FooBar source, FooBar target)
{
    source.MapTo(target, (s,t) => t.Foo = s.Foo, 
                         (s,t) => t.Bar = s.Bar, 
                         (t,s) => t.Baz = s.Baz);
}

class FooBar
{
    public string Foo { get; set; }
    public string Bar { get; set; }
    public string Baz { get; set; }
}

Однако, это более многословно, чем просто:

void Copy(FooBar source, FooBar target)
{
    target.Foo = source.Foo;
    target.Bar = source.Bar;
    target.Baz = source.Baz;
}

Что-нибудь еще происходит в вашей копии, что делает последний пример недействительным? Если нет, то я бы просто все упростил и пошел на это.

0 голосов
/ 28 марта 2012

хорошо, я думаю, у меня что-то работает, используя Expression и Reflection.

thing1.MapTo(thing2, x => new { x.Prop1, x.Prop2 });

и

public static void MapTo<T, P>(
    this T source, T target, Expression<Func<T, P>> expr)
{
    (expr.Body as NewExpression).Members.ToList()
        .ForEach(m => source.Copy(target, m.Name));
}

public static void Copy<T>(this T source, T target, string prop)
{
    var p = typeof(T).GetProperty(prop);
    p.SetValue(target, p.GetValue(source, null), null);
}

Я не уверен, что методы имеют лучшие имена, ноэто начало, и оно позволяет использовать то, на что я надеялся.Есть проблемы с этим подходом?

...