[英]How to set a private lazy<T> with reflection for testing purposes in C#?
We have a pretty big system, that used to eager load data into properies with private setters. 我们有一个非常大的系统,过去常常用私人设置器将数据加载到属性中。 For using testing specific scenarios, I used to write data in those properties with private setters.
对于使用测试特定方案,我曾经使用私有setter在这些属性中写入数据。
However, because the system was getting slow, and was loading unnessesary things, we changed certain things to lazy loading, using the Lazy class. 但是,由于系统变慢,并且正在加载无关紧要的东西,我们使用Lazy类将某些内容更改为延迟加载。 However, now I'm no longer able to write data into those properties, so a lot of unit tests won't run anymore.
但是,现在我不再能够将数据写入这些属性,因此很多单元测试将不再运行。
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());
}
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;
}
}
Now keep in mind, this is only one sample. 现在请记住,这只是一个样本。 The changes to code from eager loading to lazy loading is changed on a lot of different places.
在很多不同的地方改变了从急切加载到延迟加载的代码更改。 Because we don't want to change the code for all tests, the best option seemed to be to change the middleman: the
ReflectionHelper
因为我们不想更改所有测试的代码,所以最好的选择似乎是改变中间人:
ReflectionHelper
ReflectionHelper
ReflectionHelper
的当前状态 Btw, I would like to apologize in advance for this weird piece of code 顺便说一句,我想提前为这段奇怪的代码道歉
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);
}
}
The first problem I came across attempting to do this, is that I was unable to create a delegate at runtime of an unknown type, so I tried to get around that by trying to set the internal values of the Lazy<T>
instead. 我遇到的第一个问题是,我无法在运行时创建未知类型的委托,所以我尝试通过尝试设置
Lazy<T>
的内部值来解决这个问题。
After setting the internal values of the lazy, I could see it was indeed set. 在设置了懒惰的内部值之后,我可以看到它确实设置了。 However the problem I ran into doing that, was that I found out the internal field of a
Lazy<T>
is not a <T>
, but actually a Lazy<T>.Boxed
. 然而我遇到的问题是,我发现
Lazy<T>
的内部字段不是<T>
,而实际上是Lazy<T>.Boxed
。 Lazy<T>.Boxed
is an internal class of lazy, so I'd have to instantiate that somehow... Lazy<T>.Boxed
是一个懒惰的内部类,所以我必须以某种方式实例化...
I realized that maybe I'm approaching this problem from the wrong direction, since the solution is getting exponentially more complex, and I doubt many people will understand the weird metaprogramming of the 'ReflectionHelper'. 我意识到也许我正在从错误的方向接近这个问题,因为解决方案变得越来越复杂,我怀疑很多人会理解'ReflectionHelper'的奇怪的元编程。
What would be the best approach in solving this? 解决这个问题的最佳方法是什么? Can I solve this in the
ReflectionHelper
or will I have to go through every unittest and modify those? 我可以在
ReflectionHelper
解决这个问题,还是我必须通过每个单元测试并修改它们?
I got a answer from dasblinkenlight to make SetProperty generic. 我从dasblinkenlight得到了一个答案,使SetProperty变得通用。 I changed to code, and this is the end result, in case someone else needs it
我改为代码,这是最终结果,以防其他人需要它
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);
}
}
Setting variables to null no longer work without explicitly giving it a type. 将变量设置为null不再有效而不显式赋予它类型。
ReflectionHelper.SetProperty(instance, "parameter", null);
has to become 必须成为
ReflectionHelper.SetProperty<object>(instance, "parameter", null);
Try making SetProperty
a generic method: 尝试使
SetProperty
成为通用方法:
public static void SetProperty<T>(object instance, string properyName, T value)
This should let you capture the type of value
. 这应该可以捕获
value
的类型。 With T
in place, you could construct Lazy<T>
object in the regular C# syntax, rather than going through reflection: 使用
T
,您可以在常规C#语法中构造Lazy<T>
对象,而不是通过反射:
...
Lazy<T> lazyValue = new Lazy<T>(() => value);
...
Now you can write the lazyValue
into the property/field with the setValue
call. 现在,您可以使用
setValue
调用将lazyValue
写入属性/字段。
This should be sufficient for many, if not all, of your unit tests. 对于许多(如果不是全部)单元测试,这应该足够了。
To make your classes unit-testable, and to promote the separation of concerns, consider using Dependency Injection: 要使您的类可单元测试,并促进关注点分离,请考虑使用依赖注入:
public class ComplexClass
{
private readonly Lazy<DateTime> _date;
public DateTime Date
{
get
{
return _date.Value;
}
}
public ComplexClass(Lazy<DateTime> date)
{
// Allow your DI framework to determine where dates come from.
// This separates the concern of date resolution from this class,
// whose responsibility is mostly around determining information
// based on this date.
_date = date;
}
public string GetDay()
{
if (Date.Day == 1 && Date.Month == 1)
{
return "New year!";
}
return string.Empty;
}
}
[Test]
public void TestNewyear()
{
var newYear = new DateTime(2014, 1, 1);
var complexClass = new ComplexClass(new Lazy<DateTime>(() => newYear));
Assert.AreEqual("New year!", complexClass.GetDay());
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.