ラムダ式の繰り返しを排除するリファクタリング
-
03-07-2019 - |
質問
これら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のサブクラスです。
解決
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
};
}
他のヒント
まあ、私はあなたがそれを行うことができる本当に恐ろしい方法を持っています。
特定のタイプのすべてのプロパティを処理するためにリフレクション(私に耐えてください!)を使用し、そのタイプから別のタイプにプロパティをコピーする(Reflection.Emitを使用して)デリゲートを構築するメソッドを書くことができます。次に、匿名型を使用して、コピーするデリゲートを一度だけ構築するだけで済むようにします。メソッドは次のようになります。
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はジェネリックメソッドであるため、「ターゲット」を明示的に指定できます。タイプしますが、暗黙的に&quot; source&quot;を指定しますタイプ。
- 射影初期化子を使用して、匿名型の初期化に使用される式からプロパティの名前を推測しています
本当に価値があるかどうかはわかりませんが、とても楽しいアイデアです...とにかくそれを実装する必要があるかもしれません:
編集: 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" });
名前には繰り返しがありますが、C#は、あるクラスのPropertyAが別のクラスのPropertyAに接続されていることを知りません。明示的に接続する必要があります。あなたがそれをやった方法はうまくいきます。これらが十分にある場合は、リフレクションを使用して、クラスの任意のペアに対してこれを実行できる1つのメソッドを記述することを検討してください。
選択した方法がパフォーマンスに与える影響に注意してください。反射自体は遅くなります。ただし、リフレクションを使用してILを放射することもできます。ILが放射されると、記述した速度と同じ速度で実行されます。また、式ツリーを生成して、コンパイル済みデリゲートに変換することもできます。これらの手法はやや複雑であるため、トレードオフを検討する必要があります。
呼び出し側に、必要なプロパティのみを持つ匿名型の独自のオブジェクトを返させることができます:
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});