2008-11-12 18 views
5

No puedo entender por qué lo siguiente no funciona, ¿alguna idea? interfaz pública IFieldSimpleItem {}"Restricciones para la implementación de interfaz explícita ..."

public interface IFieldNormalItem : IFieldSimpleItem 
{ } 

public class Person 
{ 
    public virtual T Create<T>() 
     where T : IFieldSimpleItem 
    { 
     return default(T); 
    } 
} 

public class Bose : Person 
{ 
    public override T Create<T>() 
     where T : IFieldNormalItem //This is where the error is 
    { 
     return default(T); 
    } 
} 

La razón por la que estoy haciendo esto es debido al hecho de que si un desarrollador hereda de Bose, Bose se basa en la creación de la instancia de ser al menos de IFieldNormalItem. Mientras que lo siguiente solo depende de que sea IFieldSimpleItem, pero lo anterior debería forzarlo a ser al menos IFieldNormalItem.

public class Person 
{ 
    public virtual IFieldSimpleItem Create() 
    { 
     return null; 
    } 
} 

public class Bose : Person 
{ 
    public override IFieldSimpleItem Create() 
    { 
     return null; 
    } 
} 

Saludos Anthony

Respuesta

2

estoy bastante seguro de que está fuera de suerte por lo que usando el compilador y genéricos para ahorrar algunas comprobaciones en tiempo de ejecución. No puede anular algo que aún no existe, y no puede tener diferentes tipos de devolución para los mismos métodos.

No puedo decir que entiendo completamente su motivación, pero tiene un mérito técnico.

Mi primer intento fue utilizar la clase base con una interfaz pública no virtual y luego tener otro método virtual protegido CheckCreatedType que permitiera que cualquier elemento de la cadena inspeccionara el tipo antes de llamar a la clase base Crear.

public class A 
{ 
    public IFieldSimpleItem Create() 
    { 
     IFieldSimpleItem created = InternalCreate(); 
     CheckCreatedType(created); 
     return created; 
    } 

    protected virtual IFieldSimpleItem InternalCreate() 
    { 
     return new SimpleImpl(); 
    } 
    protected virtual void CheckCreatedType(IFieldSimpleItem item) 
    { 
     // base class doesn't care. compiler guarantees IFieldSimpleItem 
    } 
} 
public class B : A 
{ 
    protected override IFieldSimpleItem InternalCreate() 
    { 
     // does not call base class. 
     return new NormalImpl(); 
    } 
    protected override void CheckCreatedType(IFieldSimpleItem item) 
    { 
     base.CheckCreatedType(item); 
     if (!(item is IFieldNormalItem)) 
      throw new Exception("I need a normal item."); 

    } 
} 

Los siguientes controles en el tiempo de ejecución se comprueban en la clase base.El problema irresoluble es que todavía tiene que confiar en el método de clase base que se llama. Una subclase que se comporta mal puede romper todos los controles al no llamar al base.CheckCreatedType(item).

Las alternativas son codificar todas las verificaciones de todas las subclases dentro de la clase base (malas) o externalizar la verificación.

Intento 2: (Sub) Las clases registran los cheques que necesitan.

public class A 
{ 
    public IFieldSimpleItem Create() 
    { 
     IFieldSimpleItem created = InternalCreate(); 
     CheckCreatedType(created); 
     return created; 
    } 

    protected virtual IFieldSimpleItem InternalCreate() 
    { 
     return new SimpleImpl(); 
    } 

    private void CheckCreatedType(IFieldSimpleItem item) 
    { 
     Type inspect = this.GetType(); 
     bool keepgoing = true; 
     while (keepgoing) 
     { 
      string name = inspect.FullName; 
      if (CheckDelegateMethods.ContainsKey(name)) 
      { 
       var checkDelegate = CheckDelegateMethods[name]; 
       if (!checkDelegate(item)) 
        throw new Exception("failed check"); 
      } 
      if (inspect == typeof(A)) 
      { 
       keepgoing = false; 
      } 
      else 
      { 
       inspect = inspect.BaseType; 
      } 
     } 
    } 

    private static Dictionary<string,Func<IFieldSimpleItem,bool>> CheckDelegateMethods = new Dictionary<string,Func<IFieldSimpleItem,bool>>(); 
    protected static void RegisterCheckOnType(string name, Func<IFieldSimpleItem,bool> checkMethod) 
    { 
     CheckDelegateMethods.Add(name, checkMethod); 
    } 
} 
public class B : A 
{ 
    static B() 
    { 
     RegisterCheckOnType(typeof(B).FullName, o => o is IFieldNormalItem); 
    } 

    protected override IFieldSimpleItem InternalCreate() 
    { 
     // does not call base class. 
     return new NormalImpl(); 
    } 
} 

La comprobación se realiza por la subclase registrar un delegado para invocar en la clase base, pero sin la clase base conocer todas las reglas por adelantado. Tenga en cuenta también que sigue siendo la interfaz pública no virtual que permite a la clase base comprobar los resultados antes de devolverlos.

Supongo que es un error del desarrollador que está tratando de atrapar. Si es aplicable, puede adornar el método de comprobación de tiempo de ejecución con System.Diagnostics.Conditional("DEBUG")], lo que permite que la versión Release omita las comprobaciones.

Mi conocimiento de los genéricos no es perfecto, así que quizás esto sea innecesario. Sin embargo, los controles aquí no tienen que ser solo para tipo: esto podría adaptarse para otros usos. p.ej. el delegado pasado en Register.. no tiene que simplemente verificar que la referencia es un tipo específico '

* Tenga en cuenta que probablemente no sea bueno crear el diccionario en el nombre de tipo como se escribió anteriormente; este trabajo es un poco simplista para ilustrar el mecanismo utilizado.

+0

Gracias, aunque esta comprobación se realiza en tiempo de ejecución, creo que esto es lo más cercano que voy a conseguir. Cheers – vdhant

1

Creo que el problema es que se reemplaza un método previamente definido. Así que efectivamente intentas cambiar la definición del método, lo cual no está permitido. Su única opción es crear un nuevo método, p.

public class Bose : Person 
{ 
    public virtual T CreateNormal<T>() 
     where T : IFieldNormalItem //This is where the error is 
    { 
     return default(T); 
    } 
} 

o requieren un campo normal en la clase Person o realizan la validación de forma dinámica.

+0

Hubiera pensado que estaría bien porque estoy haciendo que la definición sea más fuerte, no más débil. – vdhant

0

Qué tal esto:

public interface IFieldNormalItem : IFieldSimpleItem 
{ } 

public class Person<T> where T : IFieldSimpleItem 
{ 
    public virtual T Create() 
    { 
     return default(T); 
    } 
} 

Ahora usted puede tener Person<IFieldSimpleItem> (corresponde a Person) o Person<IFieldNormalItem> (corresponde a Bose).

+0

¿Es correcto hacer que la clase sea genérica para crear un método genérico? –

+0

Considero que este es un caso de "Esta es una persona que tiene campos simples" frente a "Esta es una persona que tiene campos normales". En este caso, en realidad estás diferenciando a la persona y hacer que la clase genérica sea aceptable. – tvanfosson

+0

desafortunadamente yapiskan tiene razón. En este caso, no puedo hacer que toda la clase sea genérica (es decir, Person ) debido a algún casting que tengo que hacer. – vdhant

0

El código siguiente es suficiente para anular. El tipo T ya está indicado como necesario para ser implementado por IFieldSimpleItem en la clase base Persona.

public class Bose : Person 
{ 
    public override T Create<T>() 
     // where T : IFieldNormalItem // You don't need this line. 
    { 
     return default(T); 
    } 
} 

EDIT: Estoy totalmente de consiguió la pregunta equivocada por lo que el código anterior no va a resolver este caso. Lo único que tienes que hacer es; no anular el método Create por "anulación" sino "virtual".

public class Bose : Person 
{ 
    public virtual T Create<T>() 
     where T : IFieldNormalItem 
    { 
     return default(T); 
    } 
} 
+0

Pero eso solo exigiría el uso de al menos IFieldSimpleItem por parte de create. El problema es que si algo hereda de Bose y anula la creación con algo que devuelve una implementación de IFieldSimpleItem, producirá un error porque Bose necesita que sea al menos IFieldNormalItem. – vdhant

+1

Me salió mal su pregunta. Por cierto, actualicé mi respuesta. –

1

Parece que no se puede cambiar la definición del método, pero se puede hacer que sus clases genérico en lugar del método de crear?

public class Person<T> where T : IFieldSimpleItem 
{ 
    public virtual T Create() 
    { 
     return default(T); 
    } 
} 

public class Bose<T> : Person<T> where T : IFieldNormalItem 
{ 
    public override T Create() 
    { 
     return default(T); 
    } 
} 
+0

desafortunadamente no puedo hacer esto. Acabo de pasar siglos eliminando el estilo de los genéricos de mi código. En este caso, no puedo hacer que toda la clase sea genérica (es decir,Persona ) debido a algún casting que tengo que hacer. – vdhant

0

El ejemplo más simple es que esto rompe el polimorfismo. Si tiene una colección de Persona, donde uno o más de esos elementos es del tipo Bose, se bloqueará tan pronto como llegue a Bose.

Person[] people; 
[...initialize this somewhere...] 

foreach(Person p in people) 
    p.Create<IFieldSimpleItem>(); 
+0

¿Por qué se cuelga? –

1

Al cambiar la restricción genérica se cambia la firma del método, que no está permitida si está anulando una virtual.

creo que puede que tenga que dividir el método Create en una clase separada:

public interface IFieldSimpleItem { } 

public interface IFieldNormalItem : IFieldSimpleItem{ } 

public interface IFieldCreator<TField, TPerson> where TField : IFieldSimpleItem where TPerson : Person 
{ 
    TField Create(TPerson person); 
} 

public class Person 
{ 
} 

public class Bose : Person 
{ 
} 

public class PersonFieldCreator : IFieldCreator<IFieldSimpleItem, Person> 
{ 
    public IFieldSimpleItem Create(Person person) { return null; } 
} 

public class BoseFieldCreator : IFieldCreator<IFieldNormalItem, Bose> 
{ 
    public IFieldNormalItem Create(Bose person) { return null; } 
} 
+0

El problema es que @vdhant tiene una restricción que quiere asegurar que las subclases de Bose creen un tipo que sea al menos un IFieldNormalItem –

+0

En este ejemplo, BoseFieldCreator funcionaría para las clases derivadas de Bose. –

2

eso no está permitido porque viola principio de sustitución de liskov.

Digamos que usted tiene otra interfaz:

public interface IFieldSuperItem : IFieldSimpleItem 

A continuación, puede hacer esto

Person p = new Boss(); 
p.Create<IFieldSuperItem>(); 

La llamada en la segunda línea, mientras que es compatible con la definición de Crear en persona, pero, obviamente, no es compatible con eso definido en Boss (que solo funciona con IFieldNormalItem y su subclase).

Cuestiones relacionadas