Given the following class
public class Anonymizer : IAnonymizer
{
public void Anonymize<TEntity, TProperty>(TEntity entity, Expression<Func<TEntity, TProperty>> propertySelector)
{
var value = typeof(TProperty) switch
{
{ } t when IsNullable(t) => (TProperty?)(object) null!,
{ } t when t == typeof(string) => (TProperty) (object) "<Anonymized>",
{ } t when t == typeof(int) => (TProperty) (object) -1,
_ => default,
};
var memberExpression = (MemberExpression)propertySelector.Body;
var property = (PropertyInfo)memberExpression.Member;
property.SetValue(entity, value);
}
private static bool IsNullable(Type t)
{
return !t.IsValueType || Nullable.GetUnderlyingType(t) != null;
}
}
and the following tests
[Fact]
public void Test_string()
{
var sut = new Anonymizer();
var myObject = new MyClass {MyText = "Hello World", MyNumber = 42};
sut.Anonymize(myObject, e => e.MyText);
myObject.MyText.Should().Be("<Anonymized>");
myObject.MyNumber.Should().Be(42);
}
[Fact]
public void Test_int()
{
var sut = new Anonymizer();
var myObject = new MyClass {MyText = "Hello World", MyNumber = 42};
sut.Anonymize(myObject, e => e.MyNumber);
myObject.MyText.Should().Be("Hello World");
myObject.MyNumber.Should().Be(-1);
}
[Fact]
public void Test_NullableString()
{
var sut = new Anonymizer();
var myObject = new MyClass
{
MyText = "Hello World",
MyNumber = 42,
MyNullableString = "Hello World"
};
sut.Anonymize(myObject, e => e.MyNullableString);
myObject.MyNumber.Should().Be(42);
myObject.MyNullableString.Should().BeNull();
}
class MyClass
{
public string MyText { get; set; }
public int MyNumber { get; set; }
public string? MyNullableString { get; set; }
}
It seems like there is no way to distinguish between a property of type "string" and "string?". It seems like TProperty will be "string" in both cases.
Any suggestions
You need to analyze the NullabilityInfoContext
, something along these lines:
public class Anonymizer : IAnonymizer
{
public void Anonymize<TEntity, TProperty>(TEntity entity, Expression<Func<TEntity, TProperty>> propertySelector)
{
var value = typeof(TProperty) switch
{
{ } t when IsNullableValueType(t) => (TProperty?)(object) null!,
{ } t when IsNullableReferenceType(propertySelector) => (TProperty?)(object) null!,
// ...
};
// ...
}
private static bool IsNullableValueType(Type t) => Nullable.GetUnderlyingType(t) != null;
private static bool IsNullableReferenceType<TEntity, TProperty>(Expression<Func<TEntity, TProperty>> propertySelector)
{
if (typeof(TProperty).IsValueType) return false;
var memberExpression = (MemberExpression)propertySelector.Body;
var property = (PropertyInfo)memberExpression.Member;
NullabilityInfoContext context = new();
var nullabilityInfo = context.Create(property);
return nullabilityInfo.ReadState == NullabilityState.Nullable;
}
}
Note that invoking reflection in this manner every time can be costly (or even not available in some cases), so you should consider following options: