2012-02-22 9 views
6

Quiero escribir algo como lo siguiente:¿Cómo evitar que una clase abstracta con clases públicas derivadas se herede en otros ensambles?

internal class InternalData 
    { 
    } 

    public class PublicData 
    { 
    } 

    abstract internal class Base { 
     internal Base() { } 

     private static InternalData CreateInternalDataFromPublicData(PublicData publicData) 
     { 
      throw new NotImplementedException(); 
     } 

     abstract protected void DoProcess(InternalData internalData); 

     public void Process(PublicData publicData) 
     { 
      InternalData internalData = CreateInternalDataFromPublicData(publicData); 
      DoProcess(internalData); 
     } 
    } 

    public sealed class Derived : Base 
    { 
     protected override void DoProcess(InternalData internalData) 
     { 
      throw new NotImplementedException(); 
     } 
    } 

Es decir, Base contiene algo de lógica interna y no está destinada a ser heredado por clases fuera de mi montaje; y Derived es accesible desde el exterior. InternalData también contiene un poco de lógica interna y, como lo haría (y nunca debería) ser utilizado desde el exterior, también quiero que sea interno.

Por supuesto, el código anterior no se compilará, ya que Base no debería ser menos accesible que Derived. Puedo configurar el Base para que sea public, eso está bien, pero lleva a otro problema. Si Base es público, entonces podría haber alguno ExternalDerived : Base en otro ensamblaje. Pero Base.DoProcess acepta un argumento InternalData, por lo que ExternalDerived no puede implementarlo (ya que no conoce el InternalData). El constructor interno sin parámetros Base impide la creación de instancias ExternalDerived, y por lo tanto, nadie implementará ExternalDerived.DoProcess y no se necesita la exposición pública InternalData, pero el compilador no lo sabe.

¿Cómo puedo reescribir el código anterior para que haya un método abstracto DoProcess(InternalData) y para que la clase InternalData sea interna?

Respuesta

0

El conjunto Base es public.

public abstract class Base {... 

Cambio Base.DoProcess:

protected virtual void DoProcess<T>(T internalData) 
{ 
    if (!(internalData is InternalData)) 
    { 
     throw new ArgumentOutOfRangeException("internalData"); 
    } 
} 

Cambio Derived.DoProcess:

protected override void DoProcess<T>(T internalData) 
{ 
    base.DoProcess(internalData); 
    // Other operations 
} 
+1

¡Guau, esa es ciertamente una manera sucia en la que nunca pensé! Estoy aceptando su respuesta, ya que parece ser la única que realmente resuelve el problema, aunque voy a mejor sólo vivo con el 'InternalData' hecho público en lugar :) – penartur

+0

@penartur Todavía no explicó lo que le impide simplemente haciendo 'DoProcess' interno. 'DoProcess interno 'aunque no es óptimo, es ciertamente más limpio que este truco. – CodesInChaos

+0

Como ya he dicho, no voy a implementar este truco en mi código, y estoy exponiendo 'InternalData' en su lugar. En cuanto a su pregunta, básicamente la elección es entre 'InternalData interno; DoProcess interno y 'InternalData público; DoProcess protegido; aunque preferiría 'InternalData interno; DoProcess protegido 'si hubiera tal opción, de estos dos preferiré el último. – penartur

1

Huele como debería usar composition instead of inheritance. lo siento, esta es una respuesta muy vaga. Estoy pensando más sobre esto ahora ..

+0

Su enlace no funciona. La composición tiene sentido con la muestra de código exacta que publiqué, pero en mi herencia de código real tiene más sentido. Básicamente, tuve una clase con muchos métodos y campos privados; y luego necesité extenderlo, de modo que algunos de los métodos privados se protegieran, lo que dio como resultado el siguiente problema. Hacer uso de la composición aquí aparentemente resultaría en escribir un montón de código, y, después de todo, en mi caso real el 'Derived' ** en realidad es ** un caso específico de' Base'. – penartur

+0

Se arregló el enlace, de todos modos solo es una búsqueda de Google. Sin ver todo el código, es difícil ser más específico. Sí, la composición a menudo resulta en escribir más código, pero eso no es necesariamente algo malo. –

+0

¿Las clases de datos son inmutables POCO sin métodos? –

4

Para hacer InternalData interna, DoProcess debe ser private o internal (o InternalAndProtected, pero C# no es compatible con esta función CLR). No puede ser protected o protected internal.

internal abstract DoProcess(InternalData internalData); 

yo probablemente también agrego un miembro de internal abstract void DoNotInheritFromThisClassInAnOutsideAssembly(). Eso evita que cualquiera fuera del ensamblado herede de su clase, porque no pueden implementar ese miembro y obtienen un error de compilación razonable. Pero no puede hacer que la clase Base sea interna.


Consideraré la refacturación del código, para que no tenga una clase base común. Probablemente usando algunas interfaces y composición internal.

+0

. Traté de +2, pero no funcionó. – zmbq

+0

Ya existe 'base interna() {}'. – penartur

+0

@penartur Tener un método da un mejor error de compilación. – CodesInChaos

1

El tipo de base debe ser accesible, porque de lo contrario, se hace imposible averiguar su base . Su Base deriva directamente de System.Object, pero ¿cómo sabe un usuario de Derived? ¿Cómo sabe que Base no se deriva de otro tipo público, y los miembros de ese tipo deberían estar disponibles?

Si marca todo en Base interno, a excepción de la propia clase, ya ha impedido que otros ensambles hagan algo útil con ella. En otras palabras, si hace DoProcess interno, puede evitar que InternalData se convierta en public.

Sí, admite que esto permite errores en su propio ensamblaje, si otras clases intentan llamar al DoProcess. Desafortunadamente, no existe un modificador de acceso "accesible desde clases derivadas en el mismo ensamblado", solo "accesible desde clases derivadas", "accesible desde el mismo ensamblado" y "accesible desde clases derivadas y accesible desde el mismo ensamblado". (En realidad, .NET sí lo admite, pero C# no).

+0

"_El tipo de base debe ser accesible, porque de lo contrario, se vuelve imposible averiguar su base_" - Lo entiendo, y hacer que 'Base' sea público está bien para mí. Su explicación sobre la falta de un modificador de acceso apropiado en C# tiene sentido, y hasta ahora, es la mejor respuesta. Aún así, por 'base interna() {}' es posible decir que nadie implementará 'DoProcess' en otros ensamblados y que este argumento no necesita ser público, por lo que tal vez haya alguna solución alternativa. – penartur

+1

correcta, pero es mucho más difícil de darse cuenta de eso, ya que también se basa en el hecho de que todas las clases en su propia asamblea que se derivan de '' base' son sealed'. – hvd

-2

En realidad, es bastante sencillo. Solo requiere que una clase derivada implemente un método interno abstracto. Las clases fuera de la biblioteca no podrán implementar el método abstracto y, por lo tanto, fallarán en el momento de la compilación.

Su ejemplo, minimizado en los aspectos básicos:

abstract internal class Base { 
    internal protected abstract void DoProcess(); 

    public void Process() { 
     DoProcess(); 
    } 
} 

public sealed class Derived : Base { 
    internal protected override void DoProcess() { 
     throw new NotImplementedException(); 
    } 
} 
+0

¿Has intentado compilar tu código? Las clases derivadas no pueden ser más accesibles que las básicas por razones obvias. No es posible derivar una clase pública de la interna. – penartur

+0

@penartur: El ejemplo no es bueno, pero el truco es viable siempre que todas las clases sean abstractas o estén selladas. – supercat

Cuestiones relacionadas