Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get by reflection properties of class with private setters

I am trying to get all properties of a class that have a private setter. This seems very simple, however I run into a strange, but probably normal, behavior.

This is the solution I have implemented

public static IEnumerable<PropertyInfo> InstanceProperties(this Type type) {
    List<PropertyInfo> results = new();
    var allProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
    foreach (var property in allProperties) {
        var setMethod = property.GetSetMethod(true);
        if (setMethod != null && setMethod.IsPrivate) {
            results.Add(property);
        }
    }
    return results;
}

I have created some simple classes for testing as well

public class BaseClass {
    public string Name { get; private set; }
    public BaseClass(string name) {
        Name = name;
    }
}

public class DerivedClass : BaseClass {
    public string Description { get; private set; }
    public DerivedClass(string name, string description) : base(name) {
        Description = description;
    }
}

To test this I am using

[Fact]
public void InstanceProperties_WithBaseType_ReturnsAllProperties() {
    // Arrange
    Type baseEntityType = typeof(BaseClass);
    // Act
    var result = baseEntityType.InstanceProperties();
    // Assert
    result.Should().NotBeNullOrEmpty();
    result.Count().Should().Be(1);
}

[Fact]
public void InstanceProperties_WithDerivedType_ReturnsAllProperties() {
    // Arrange
    Type baseEntityType = typeof(DerivedClass);
    // Act
    var result = baseEntityType.InstanceProperties();
    // Assert
    result.Should().NotBeNullOrEmpty();
    result.Count().Should().Be(2);
}

The first test succeds while the second test fails. More specifically in second test, the code var allProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); returns two properties however the property of the base class does not have a setter method. This is not properly coherent with the first test as in that case the property does have a set method which is defined as private.

Any suggestion/explanation about this behavior?

EDIT As per suggestions from comments I better explain the scope of the question. The final objective is to constructs some types of objects that have the following characteristics:

  • Can be classes inheriting directly from a well known base class (e.g. Entity) which have a single unique instance property of type Guid with a private setter
  • Can be classes inheriting from an abstract base class that in turn inherits from Entity
  • All instance properties have a private setter
  • All classes have a single public static factory method called Create with, of course, different parameters for each class

I would like to understand how this construction technique is handled by Entity Framework which uses a private parameterless constructor to create object instances.

EDIT 2

I've ended up writing a solution which is very similar to the two presented by Jamiec and Gert Arnold. I am writing here for reference. Thanks everybody!

public static IEnumerable<PropertyInfo> InstanceProperties(this Type type) {
    List<PropertyInfo> results = new();
    Type? leafType = type;
    while (leafType != null) {
        var allProperties = leafType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (var property in allProperties) {
            var accessors = property
                .GetAccessors(true)
                .Where(a => a.ReturnType == typeof(void) && a.IsPrivate)
                .ToArray();
            foreach (var accessor in accessors) {
                results.Add(property);
            }
        }
        leafType = leafType.BaseType;
    }
    return results;
}
like image 342
Mammt Avatar asked Sep 14 '25 06:09

Mammt


1 Answers

This could be as easy as a bit of recursion, if the type has a base type, also add those properties to your list:

public static IEnumerable<PropertyInfo> InstanceProperties(this Type type)
{
    List<PropertyInfo> results = new();
    var allProperties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
    foreach (var property in allProperties)
    {
        var setMethod = property.GetSetMethod(true);
        if (setMethod != null && setMethod.IsPrivate)
        {
            results.Add(property);
        }
    }
    if(type.BaseType != null)
    {
        results.AddRange(type.BaseType.InstanceProperties());
    }
    return results;
}

This gives your expected output of 2 in this specific example, but might need some tweaking to match your exact expectation.

like image 119
Jamiec Avatar answered Sep 15 '25 21:09

Jamiec