Domanda

Questi due metodi mostrano ripetizione:

public static Expression<Func<Foo, FooEditDto>> EditDtoSelector()
{
    return f => new FooEditDto
    {
        PropertyA = f.PropertyA,
        PropertyB = f.PropertyB,
        PropertyC = f.PropertyC,
        PropertyD = f.PropertyD,
        PropertyE = f.PropertyE
    };
}

public static Expression<Func<Foo, FooListDto>> ListDtoSelector()
{
    return f => new FooDto
    {
        PropertyA = f.PropertyA,
        PropertyB = f.PropertyB,
        PropertyC = f.PropertyC
    };
}

Come posso refactoring per eliminare questa ripetizione?

AGGIORNAMENTO: Oops, ho trascurato di menzionare un punto importante. FooEditDto è una sottoclasse di FooDto.

È stato utile?

Soluzione

Se FooEditDto è una sottoclasse di FooDto e non hai bisogno di MemberInitExpressions, usa un costruttore:

class FooDto
 { public FooDto(Bar a, Bar b, Bar c) 
    { PropertyA = a;
      PropertyB = b;
      PropertyC = c;
    }
   public Bar PropertyA {get;set;}
   public Bar PropertyB {get;set;}
   public Bar PropertyC {get;set;}
 }

class FooEditDto : FooDto
 { public FooEditDto(Bar a, Bar b, Bar c) : base(a,b,c)
   public Bar PropertyD {get;set;}
   public Bar PropertyE {get;set;}
 }

public static Expression<Func<Foo, FooEditDto>> EditDtoSelector()
{
    return f => new FooEditDto(f.PropertyA,f.PropertyB,f.PropertyC)
    {
        PropertyD = f.PropertyD,
        PropertyE = f.PropertyE
    };
}

Altri suggerimenti

Beh, ho un davvero orribile in cui potresti farlo.

Potevi scrivere un metodo che usasse il riflesso (abbi pazienza con me!) per elaborare tutte le proprietà di un tipo particolare e costruire un delegato (usando Reflection.Emit) per copiare le proprietà da quel tipo a un altro. Quindi utilizza un tipo anonimo per assicurarti di dover creare il delegato per la copia una sola volta, quindi è veloce. Il tuo metodo sarebbe quindi simile a questo:

public static Expression<Func<Foo, FooEditDto>> EditDtoSelector()
{
    return f => MagicCopier<FooEditDto>.Copy(new { 
        f.PropertyA, f.PropertyB, f.PropertyC, f.PropertyD, f.PropertyC
    });
}

Le sfumature qui:

  • MagicCopier è un tipo generico e Copia è un metodo generico in modo da poter specificare in modo esplicito il "target" tipo, ma specifica implicitamente la "fonte" tipo.
  • Sta utilizzando un inizializzatore di proiezione per inferire i nomi delle proprietà dalle espressioni utilizzate per inizializzare il tipo anonimo

Non sono sicuro che ne valga davvero la pena, ma è un'idea piuttosto divertente ... Potrei doverlo implementare comunque :)

EDIT: con MemberInitExpression noi potrebbe fare tutto con un albero delle espressioni, il che rende molto più semplice di CodeDOM. Ci proverò stasera ...

EDIT: fatto, ed è in realtà un codice piuttosto semplice. Ecco la lezione:

/// <summary>
/// Generic class which copies to its target type from a source
/// type specified in the Copy method. The types are specified
/// separately to take advantage of type inference on generic
/// method arguments.
/// </summary>
public static class PropertyCopy<TTarget> where TTarget : class, new()
{
    /// <summary>
    /// Copies all readable properties from the source to a new instance
    /// of TTarget.
    /// </summary>
    public static TTarget CopyFrom<TSource>(TSource source) where TSource : class
    {
        return PropertyCopier<TSource>.Copy(source);
    }

    /// <summary>
    /// Static class to efficiently store the compiled delegate which can
    /// do the copying. We need a bit of work to ensure that exceptions are
    /// appropriately propagated, as the exception is generated at type initialization
    /// time, but we wish it to be thrown as an ArgumentException.
    /// </summary>
    private static class PropertyCopier<TSource> where TSource : class
    {
        private static readonly Func<TSource, TTarget> copier;
        private static readonly Exception initializationException;

        internal static TTarget Copy(TSource source)
        {
            if (initializationException != null)
            {
                throw initializationException;
            }
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            return copier(source);
        }

        static PropertyCopier()
        {
            try
            {
                copier = BuildCopier();
                initializationException = null;
            }
            catch (Exception e)
            {
                copier = null;
                initializationException = e;
            }
        }

        private static Func<TSource, TTarget> BuildCopier()
        {
            ParameterExpression sourceParameter = Expression.Parameter(typeof(TSource), "source");
            var bindings = new List<MemberBinding>();
            foreach (PropertyInfo sourceProperty in typeof(TSource).GetProperties())
            {
                if (!sourceProperty.CanRead)
                {
                    continue;
                }
                PropertyInfo targetProperty = typeof(TTarget).GetProperty(sourceProperty.Name);
                if (targetProperty == null)
                {
                    throw new ArgumentException("Property " + sourceProperty.Name 
                        + " is not present and accessible in " + typeof(TTarget).FullName);
                }
                if (!targetProperty.CanWrite)
                {
                    throw new ArgumentException("Property " + sourceProperty.Name 
                        + " is not writable in " + typeof(TTarget).FullName);
                }
                if (!targetProperty.PropertyType.IsAssignableFrom(sourceProperty.PropertyType))
                {
                    throw new ArgumentException("Property " + sourceProperty.Name
                        + " has an incompatible type in " + typeof(TTarget).FullName);
                }
                bindings.Add(Expression.Bind(targetProperty, Expression.Property(sourceParameter, sourceProperty)));
            }
            Expression initializer = Expression.MemberInit(Expression.New(typeof(TTarget)), bindings);
            return Expression.Lambda<Func<TSource,TTarget>>(initializer, sourceParameter).Compile();
        }
    }

E chiamandolo:

TargetType target = PropertyCopy<TargetType>.CopyFrom(new { First="Foo", Second="Bar" });

La ripetizione è nei nomi, ma C # non ha idea che PropertyA in una classe sia connessa a PropertyA in un'altra. Devi effettuare esplicitamente la connessione. Il modo in cui lo hai fatto funziona bene. Se ne avessi abbastanza di questi, potresti prendere in considerazione l'uso di reflection per scrivere un metodo in grado di farlo per qualsiasi coppia di classi.

Presta attenzione alle implicazioni sulle prestazioni di qualunque metodo tu scelga. La riflessione di per sé è più lenta. Tuttavia, potresti anche usare la riflessione per emettere IL che, una volta emesso, funzionerebbe altrettanto velocemente di quello che hai scritto. È inoltre possibile generare un albero delle espressioni e convertirlo in un delegato compilato. Queste tecniche sono piuttosto complicate, quindi devi valutare i compromessi.

Puoi consentire al chiamante di restituire il proprio oggetto di tipo anonimo con solo le proprietà di cui hanno bisogno:

public static Expression<Func<Foo,T>> 
                             GetSelector<T>(Expression<Func<Foo,T>> f)
 { return f;
 }

/* ... */
var expr = GetSelector(f => new{f.PropertyA,f.PropertyB,f.PropertyC});
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top