Estou tentando obter todas as propriedades de uma classe que tem um setter privado. Isso parece muito simples, no entanto, me deparo com um comportamento estranho, mas provavelmente normal.
Esta é a solução que implementei
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;
}
Criei algumas classes simples para teste também
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;
}
}
Para testar isso estou usando
[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);
}
O primeiro teste é bem-sucedido enquanto o segundo falha. Mais especificamente no segundo teste, o código var allProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
retorna duas propriedades, porém a propriedade da classe base não tem um método setter. Isso não é propriamente coerente com o primeiro teste, pois nesse caso a propriedade tem um método set que é definido como privado.
Alguma sugestão/explicação sobre esse comportamento?
EDIT Conforme sugestões dos comentários, explico melhor o escopo da questão. O objetivo final é construir alguns tipos de objetos que tenham as seguintes características:
- Podem ser classes que herdam diretamente de uma classe base bem conhecida (por exemplo
Entity
) que têm uma única propriedade de instância exclusiva do tipo Guid com um setter privado - Podem ser classes que herdam de uma classe base abstrata que por sua vez herda de
Entity
- Todas as propriedades de instância têm um setter privado
- Todas as classes têm um único método de fábrica estático público chamado
Create
com, é claro, parâmetros diferentes para cada classe
Gostaria de entender como essa técnica de construção é manipulada pelo Entity Framework, que usa um construtor privado sem parâmetros para criar instâncias de objetos.
EDITAR 2
Acabei escrevendo uma solução que é muito parecida com as duas apresentadas por Jamiec e Gert Arnold. Estou escrevendo aqui para referência. Obrigado a todos!
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;
}
Isso pode ser tão fácil quanto um pouco de recursão. Se o tipo tiver um tipo base, adicione também essas propriedades à sua lista:
Isso fornece a saída esperada
2
neste exemplo específico, mas pode precisar de alguns ajustes para corresponder exatamente à sua expectativa.Use
yield return
e continue retornando até que o tipo não tenha um tipo base: