6

これら 2 つの方法は繰り返しを示します。

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
    };
}

この繰り返しをなくすためにリファクタリングするにはどうすればよいですか?

更新: おっと、重要な点に言及するのを怠っていました。FooEditDto は FooDto のサブクラスです。

4

4 に答える 4

2

ええと、私はあなたがそれを行うことができる本当に恐ろしい方法を持っています。

リフレクションを使用して特定の型のすべてのプロパティを処理し (Reflection.Emit を使用して)、その型から別の型にプロパティをコピーするデリゲートを作成するメソッドを作成できます。次に、匿名型を使用して、コピー デリゲートを 1 回だけ作成する必要があることを確認します。これにより、高速になります。メソッドは次のようになります。

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

ここでのニュアンス:

  • MagicCopier はジェネリック型で、Copy はジェネリック メソッドであるため、「ターゲット」型を明示的に指定できますが、「ソース」型は暗黙的に指定できます。
  • プロジェクション初期化子を使用して、匿名型の初期化に使用される式からプロパティの名前を推測しています

本当に価値があるかどうかはわかりませんが、とても楽しいアイデアです...とにかく実装する必要があるかもしれません:)

編集: MemberInitExpressionを使用すると、式ツリーですべてを実行できるため、CodeDOM よりもはるかに簡単になります。今夜やってみようかな…

編集: 完了しました。実際には非常に単純なコードです。クラスは次のとおりです。

/// <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();
        }
    }

そしてそれを呼び出す:

TargetType target = PropertyCopy<TargetType>.CopyFrom(new { First="Foo", Second="Bar" });
于 2008-10-21T16:33:00.080 に答える
1

FooEditDtoのサブクラスでFooDtoあり、MemberInitExpressions が必要ない場合は、コンストラクターを使用します。

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
    };
}
于 2008-10-21T17:01:58.650 に答える
0

呼び出し元に、必要なプロパティのみを含む匿名型の独自のオブジェクトを返させることができます。

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});
于 2008-10-21T16:44:49.340 に答える
0

名前には繰り返しがありますが、C# は、あるクラスの PropertyA が別のクラスの PropertyA に接続されていることを認識していません。明示的に接続する必要があります。あなたがやった方法はうまくいきます。これらが十分にある場合は、リフレクションを使用して、クラスの任意のペアに対してこれを実行できる 1 つのメソッドを作成することを検討してください。

どの方法を選択しても、パフォーマンスへの影響に注意してください。反射自体は遅くなります。ただし、リフレクションを使用して IL を発行することもできます。IL が発行されると、作成したものと同じ速度で実行されます。式ツリーを生成し、それをコンパイル済みデリゲートに変換することもできます。これらの手法はやや複雑であるため、トレードオフを比較検討する必要があります。

于 2008-10-21T16:30:58.167 に答える