問題の説明
私たちはかなり大きなシステムを持っています。これは、プライベートセッターを使用してデータをプロパティにロードするために使用されていました。特定のシナリオのテストを使用するために、私はプライベートセッターを使用してそれらのプロパティにデータを書き込むために使用しました。
ただし、システムの速度が低下し、不要なものを読み込んでいたため、Lazyクラスを使用して特定のものを遅延読み込みに変更しました。ただし、これらのプロパティにデータを書き込むことができなくなったため、多くの単体テストが実行されなくなりました。
私たちが持っていたもの
テストするオブジェクト:
public class ComplexClass
{
public DateTime Date { get; private set; }
public ComplexClass()
{
// Sample data, eager loading data into variable
Date = DateTime.Now;
}
public string GetDay()
{
if (Date.Day == 1 && Date.Month == 1)
{
return "New year!";
}
return string.Empty;
}
}
テストはどのように見えますか:
[Test]
public void TestNewyear()
{
var complexClass = new ComplexClass();
var newYear = new DateTime(2014, 1, 1);
ReflectionHelper.SetProperty(complexClass, "Date", newYear);
Assert.AreEqual("New year!", complexClass.GetDay());
}
上記のサンプルで使用されているReflectionHelperの実装。
public static class ReflectionHelper
{
public static void SetProperty(object instance, string properyName, object value)
{
var type = instance.GetType();
var propertyInfo = type.GetProperty(properyName);
propertyInfo.SetValue(instance, Convert.ChangeType(value, propertyInfo.PropertyType), null);
}
}
私たちが今持っているもの
テストするオブジェクト:
public class ComplexClass
{
private readonly Lazy<DateTime> _date;
public DateTime Date
{
get
{
return _date.Value;
}
}
public ComplexClass()
{
// Sample data, lazy loading data into variable
_date = new Lazy<DateTime>(() => DateTime.Now);
}
public string GetDay()
{
if (Date.Day == 1 && Date.Month == 1)
{
return "New year!";
}
return string.Empty;
}
}
それを解決しよう
これは1つのサンプルにすぎないことに注意してください。積極的な読み込みから遅延読み込みへのコードの変更は、さまざまな場所で変更されています。すべてのテストのコードを変更したくないので、最良のオプションは仲介者を変更することであるように思われました。ReflectionHelper
これが現在の状態ですReflectionHelper
ところで、この奇妙なコードについて事前に謝罪したいと思います
public static class ReflectionHelper
{
public static void SetProperty(object instance, string properyName, object value)
{
var type = instance.GetType();
try
{
var propertyInfo = type.GetProperty(properyName);
propertyInfo.SetValue(instance, Convert.ChangeType(value, propertyInfo.PropertyType), null);
}
catch (ArgumentException e)
{
if (e.Message == "Property set method not found.")
{
// it does not have a setter. Maybe it has a backing field
var fieldName = PropertyToField(properyName);
var field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
// Create a new lazy at runtime, of the type value.GetType(), for comparing reasons
var lazyGeneric = typeof(Lazy<>);
var lazyGenericOfType = lazyGeneric.MakeGenericType(value.GetType());
// If the field is indeed a lazy, we can attempt to set the lazy
if (field.FieldType == lazyGenericOfType)
{
var lazyInstance = Activator.CreateInstance(lazyGenericOfType);
var lazyValuefield = lazyGenericOfType.GetField("m_boxed", BindingFlags.NonPublic | BindingFlags.Instance);
lazyValuefield.SetValue(lazyInstance, Convert.ChangeType(value, lazyValuefield.FieldType));
field.SetValue(instance, Convert.ChangeType(lazyInstance, lazyValuefield.FieldType));
}
field.SetValue(instance, Convert.ChangeType(value, field.FieldType));
}
}
}
private static string PropertyToField(string propertyName)
{
return "_" + Char.ToLowerInvariant(propertyName[0]) + propertyName.Substring(1);
}
}
これを実行しようとして最初に遭遇した問題は、実行時に不明なタイプのデリゲートを作成できなかったため、代わりにの内部値を設定することでそれを回避しようとしましたLazy<T>
。
レイジーの内部値を設定した後、実際に設定されていることがわかりました。しかし、私がそれに遭遇した問題は、aの内部フィールドがでLazy<T>
はなく<T>
実際にはであることがわかったということでしたLazy<T>.Boxed
。Lazy<T>.Boxed
怠惰な内部クラスなので、どういうわけかそれをインスタンス化する必要があります...
解決策が指数関数的に複雑になっているため、間違った方向からこの問題に取り組んでいる可能性があることに気付きました。多くの人が「ReflectionHelper」の奇妙なメタプログラミングを理解するとは思えません。
これを解決するための最良のアプローチは何でしょうか?これを解決することはできますか、ReflectionHelper
それともすべての単体テストを実行してそれらを変更する必要がありますか?
答えを得た後に編集する
dasblinkenlightから、SetPropertyを汎用にするための回答を得ました。私はコードに変更しました、そしてこれは他の誰かがそれを必要とする場合に備えて最終結果です
ソリューション
public static class ReflectionHelper
{
public static void SetProperty<T>(object instance, string properyName, T value)
{
var type = instance.GetType();
var propertyInfo = type.GetProperty(properyName);
var accessors = propertyInfo.GetAccessors(true);
// There is a setter, lets use that
if (accessors.Any(x => x.Name.StartsWith("set_")))
{
propertyInfo.SetValue(instance, Convert.ChangeType(value, propertyInfo.PropertyType), null);
}
else
{
// Try to find the backing field
var fieldName = PropertyToField(properyName);
var fieldInfo = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
// Cant find a field
if (fieldInfo == null)
{
throw new ArgumentException("Cannot find anything to set.");
}
// Its a normal backing field
if (fieldInfo.FieldType == typeof(T))
{
throw new NotImplementedException();
}
// if its a field of type lazy
if (fieldInfo.FieldType == typeof(Lazy<T>))
{
var lazyValue = new Lazy<T>(() => value);
fieldInfo.SetValue(instance, lazyValue);
}
else
{
throw new NotImplementedException();
}
}
}
private static string PropertyToField(string propertyName)
{
return "_" + Char.ToLowerInvariant(propertyName[0]) + propertyName.Substring(1);
}
}
これの重大な変更
変数をnullに設定すると、明示的に型を指定しないと機能しなくなります。
ReflectionHelper.SetProperty(instance, "parameter", null);
になる必要があります
ReflectionHelper.SetProperty<object>(instance, "parameter", null);