2009-05-08 14 views
5

que quieren imponer a mi regla inmutable base de código con la siguiente pruebaLos objetos inmutables .net

[TestFixture] 
public class TestEntityIf 
{ 
    [Test] 
    public void IsImmutable() 
    { 
     var setterCount = 
      (from s in typeof (Entity).GetProperties(BindingFlags.Public | BindingFlags.Instance) 
      where s.CanWrite 
      select s) 
       .Count(); 

     Assert.That(setterCount == 0, Is.True, "Immutable rule is broken"); 
    } 
} 

Pasa para:

public class Entity 
{ 
    private int ID1; 
    public int ID 
    { 
     get { return ID1; } 
    } 
} 

pero no lo hace para esto:

public class Entity 
{ 
    public int ID { get; private set; } 
} 

Y aquí va la pregunta "¿WTF?"

Respuesta

3

Una pequeña modificación a las respuestas publicadas en otra parte. Lo siguiente devolverá un valor distinto de cero si hay al menos una propiedad con un setter protegido o público. Tenga en cuenta la comprobación de GetSetMethod devolver nulo (sin setter) y la prueba de IsPrivate (es decir, no pública o protegida) en lugar de IsPublic (solo público).

var setterCount = 
      (from s in typeof(Entity).GetProperties(
       BindingFlags.Public 
       | BindingFlags.NonPublic 
       | BindingFlags.Instance) 
      where 
       s.GetSetMethod(true) != null // setter available 
       && (!s.GetSetMethod(true).IsPrivate) 
      select s).Count(); 

Sin embargo, como pointed out in Daniel Brückner's answer, el hecho de que una clase no tiene definidores de propiedad pública visibles es una condición necesaria, pero no suficiente para la clase para ser considerado inmutable.

+0

La prueba de GetMeetMethod (true)! = Null es superflua porque CanWrite == true garantiza la existencia de un setter. –

+0

Es cierto, "s.GetSetMethod (true)! = Null" es equivalente a que CanWrite sea verdadero, por lo que uno de ellos es redundante. He eliminado CanWrite porque quiero ser explícito al probar null antes de eliminar la referencia de la propiedad IsPrivate. – Joe

1

Seguramente es su private set en la segunda.

En el primero, una clase externa puede escribir su propiedad ID1 en una instancia de la clase Entity.

En este último, el colocador es privado a la clase en sí y por lo que sólo se puede llamar desde dentro de la Entidad (es decir, su propio constructor/inicializador)

Tome la private de su colocador en la segunda y debe pase

+0

ID1 es un campo de respaldo y no una propiedad en la primera clase y es solo de lectura a través del captador al mundo exterior. ¿Cómo puede ser accesible por una clase externa? –

+0

En su ejemplo original, tenía un organismo público en la primera propiedad. Él desde entonces editó la Pregunta. http://stackoverflow.com/revisions/839066/list –

+0

Ya veo. Gracias Eoin. –

2

Creo que la razón es que CanWrite dice que devuelve verdadero si hay un colocador. Un setter privado también es un setter.

Lo que me sorprende un poco es que el primero pasa, ya que tiene un organismo público, así que, a menos que todavía esté muy bajo en cafeína, el settercount es 1, por lo que la afirmación debería fallar. Ambos deberían fallar, ya que CanWrite simplemente devuelve verdadero para ambos. (y la consulta linq simplemente recupera propiedades públicas, incluida la ID, ya que es pública)

(edit) Ahora veo que ha cambiado el código de la primera clase, por lo que ya no tiene un setter.

Por lo tanto, la suposición de que CanWrite examina los descriptores de acceso del método setter no es el caso. Que debe hacer:

var setterCount = 
      (from s in typeof (Entity).GetProperties(BindingFlags.Public | BindingFlags.Instance) 
      where s.GetSetMethod().IsPublic 
      select s) 
       .Count(); 
+0

Acabo de probar el código, y estoy de acuerdo. Obtuve un conteo de 1 setter para ambas clases de prueba. –

+0

A menos que también esté demasiado bajo en caffine, no veo el colocador en el primer caso. ID1 es una variable miembro, no una propiedad, por lo que no tiene un método "setter". –

+0

exactamente. Lo que debe hacer TS en su lugar es una llamada a GetSetMethod() en la info de propiedad, y luego verificar si IsPublic es verdadero en el MethodInfo devuelto. –

7

El problema es que la propiedad es pública - a causa del captador pública - y es escribible - a causa de la incubadora privada. Deberá refinar su prueba.

Además, quiero agregar que no se puede garantizar la inmutabilidad de esta manera porque se pueden modificar los datos privados dentro de los métodos. Para garantizar la inmutabilidad, debe verificar que todos los campos estén declarados de solo lectura y que no haya propiedades implementadas automáticamente.

public static Boolean IsImmutable(this Type type) 
{ 
    const BindingFlags flags = BindingFlags.Instance | 
           BindingFlags.NonPublic | 
           BindingFlags.Public; 

    return type.GetFields(flags).All(f => f.IsInitOnly); 
} 

public static Boolean IsImmutable(this Object @object) 
{ 
    return (@object == null) || @object.GetType().IsImmutable(); 
} 

Con este método de extensión se puede probar fácilmente tipos

typeof(MyType).IsImmutable() 

e instancias

myInstance.IsImmutable() 

por su inmutabilidad.

Notas

  • Mirando a campos de instancia le permite tener propiedades de escritura, pero asegura que ahora hay campos que podrían modificarse.
  • Las propiedades implementadas automáticamente no pasarán la prueba de inmutabilidad como se esperaba debido al campo de respaldo privado y anónimo.
  • Aún puede modificar los campos readonly utilizando la reflexión.
  • Tal vez uno debería verificar que los tipos de todos los campos sean inmutables, también, porque estos objetos pertenecen al estado.
  • Esto no se puede hacer con un simple FiledInfo.FieldType.IsImmutable() para todos los campos debido a posibles ciclos y porque los tipos de base son mutables.
+0

¿Alguna razón para usar 'Object' y' Boolean' en lugar de 'object' y' bool'? –

+0

Se ven mejor (resaltado de sintaxis y mayúscula en mayúscula) y son tipos reales y no dependen de lo que el compilador genere para cadena, bool, int, objeto y todos los demás (aunque sé que el compilador no tiene otra opción)) –

+0

¿Hay buenas razones para esto? –

3

Es probable que tenga que llamar

propertyInfo.GetSetMethod().IsPublic 

porque el método set y el método get no tienen el mismo modificador de acceso, no se puede confiar en el propio PropertyInfo.

var setterCount = 
     (from s in typeof (Entity).GetProperties(
      BindingFlags.Public 
      | BindingFlags.NonPublic 
      | BindingFlags.Instance) 
     where 
      s.GetSetMethod() != null  // public setter available 
     select s) 
+0

+1 Esto es casi allí. Pero GetSetMethod() devolverá null para establecedores privados o propiedades sin setters. Necesita cambiar esto para probar nulo. – Joe

+0

@Joe: Ok, lo arreglé, entonces ya no necesito el CanWrite –

+0

No necesita el método GetMethod(). También es público, porque ProeprtyInfo.GetSetMethod() devuelve solo los métodos del conjunto público a menos que llame a ProeprtyInfo. GetSetMethod (booleano no público). –

1

Si lo entendí bien, quiere que Entity sea inmutable. Si es así, entonces la prueba se debe cambiar a

var setterCount = (from s in typeof(string).GetProperties(BindingFlags.Public | BindingFlags.Instance).Select(p => p.GetSetMethod()) 
    where s != null && s.IsPublic 
    select s).Count(); 

Assert.That(setterCount == 0, Is.True, "Immutable rule is broken"); 
+0

La afirmación es correcta: el tipo es inmutable si setterCount == 0 es verdadero y la afirmación mostrará el mensaje "La regla inmutable está rota" si esta prueba falla. –

+0

Reparado, gracias Daniel – SeeR

2

que sugieren un cambio en la condición de propiedad a:

s.CanWrite && s.GetSetMethod().IsPublic 
0

Ha intentado depurar él para ver qué propiedades cuyos CanWrite propiedad es verdadera están siendo devueltos por su consulta LINQ? Aparentemente, está recogiendo cosas que no esperas. Con ese conocimiento, puede hacer que su consulta LINQ sea más selectiva para funcionar de la forma que desee. Además, estoy de acuerdo con @Daniel Bruckner en que su clase no es completamente mutable a menos que los campos también sean de solo lectura. La persona que llama puede llamar a un método o usar un getter que puede modificar internamente los campos privados que están expuestos públicamente como de solo lectura a través de getters y esto rompería la regla inmutable, tomaría al cliente por sorpresa y causaría problemas. Las operaciones en el objeto mutable no deberían tener efectos secundarios.

Cuestiones relacionadas