Reputation: 9116
I'm designing a test API. I want to have an API like:
// There is a dynamic object which should be tested to have certain properties.
dynamic result = SomeMethod();
AssertPropertyIsNotNull(resut, o => o.Title);
AssertPropertyIsNotNull(resut, o => o.City.Name);
I want to write TestProperty
method to assert the property and shows a proper message it it fails like:
private void AssertPropertyIsNotNull(dynamic result, Func<dynamic, object> propertySelector)
{
var propertyPath = GetPropertyPathFromFunc(propertySelector);
var errorMessage = $"{propertyPath} is not filled properly."
Assert.IsNotNull(propertySelector(result), errorMessage);
}
Here in this example, I need the body for GetPropertyPathFromFunc
.
Question How can I write a method that gets a lambda like o => City.Name
as input and returns a string like "City.Name"
as result.
Upvotes: 1
Views: 755
Reputation: 20754
As you are using dynamic
you loose type safety and compile time member name checking checking so it does not make any difference to use strings as property names.
Here is a solution. It needs extensive error checking and exception handling. You can also add caching mechanism to reduce reflection overhead.
public static bool IsPropertyNull(dynamic obj, string propertyName)
{
var path = propertyName.Split('.');
object tempObject = obj;
for (int i = 0; i < path.Length; i++)
{
PropertyInfo[] dynamicProperties = tempObject.GetType().GetProperties();
var property = dynamicProperties.Single(x => x.Name == path[i]);
tempObject = property.GetValue(tempObject);
}
return tempObject == null;
}
bool isTitleNull = IsPropertyNull(result, "Title");
bool isCityNameNull = IsPropertyNull(result, "City.Name");
Upvotes: 2
Reputation: 44181
As noted, unfortunately dynamic
can't be used in expression trees as currently implemented by the C# compiler. As an alternative, you could invoke the delegate with a custom dynamic object which collects the property names accessed. I have demonstrated this below. Note that this only works with the limited syntax you have given, and I have not made a lot of effort to handle anything more complex.
private static string GetPropertyPathFromFunc(Func<dynamic, object> propertySelector)
{
var collector = new PropertyNameCollector();
propertySelector(collector);
return collector.Name;
}
private class PropertyNameCollector : DynamicObject
{
public string Name { get; private set; }
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (!string.IsNullOrEmpty(Name))
Name += ".";
Name += binder.Name;
result = this;
return true;
}
}
Upvotes: 1