2009-12-18 9 views
9

me gustaría tener la siguiente configuración:.NET: ¿cómo hacer una clase de tal manera que solo otra clase específica pueda instanciarla?

class Descriptor 
{ 
    public string Name { get; private set; } 
    public IList<Parameter> Parameters { get; private set; } // Set to ReadOnlyCollection 

    private Descrtiptor() { } 
    public Descriptor GetByName(string Name) { // Magic here, caching, loading, parsing, etc. } 
} 

class Parameter 
{ 
    public string Name { get; private set; } 
    public string Valuie { get; private set; } 
} 

Toda la estructura será de sólo lectura una vez cargada desde un archivo XML. Me gustaría que sea así, que solo la clase Descriptor pueda instanciar un parámetro.

Una forma de hacer esto sería hacer una interfaz IParameter y luego hacer Parameter clase privada en la clase de descriptor, pero en el uso del mundo real del parámetro tendrá varias propiedades, y me gustaría evitar la redefinición de ellos dos veces .

¿Es esto de alguna manera posible?

Respuesta

18

Haz que sea una clase anidada privada que implemente una interfaz particular. Entonces, solo la clase externa puede instanciarlo, pero cualquiera puede consumirlo (a través de la interfaz). Ejemplo:

interface IParameter 
{ 
    string Name { get; } 
    string Value { get; } 
} 

class Descriptor 
{ 
    public string Name { get; private set; } 
    public IList<IParameter> Parameters { get; private set; } 

    private Descriptor() { } 
    public Descriptor GetByName(string Name) { ... } 

    class Parameter : IParameter 
    { 
     public string Name { get; private set; } 
     public string Value { get; private set; } 
    } 
} 

Si realmente debe evitar la interfaz, se puede crear una clase abstracta pública que tiene todas las propiedades, pero declara un constructor protegido. A continuación, puede crear una clase anidada privada que hereda del resumen público que solo puede crear la clase externa y devolver instancias de la misma como tipo base. Ejemplo:

public abstract AbstractParameter 
{ 
    public string Name { get; protected set; } 
    public string Value { get; protected set; } 
} 

class Descriptor 
{ 
    public string Name { get; private set; } 
    public IList<AbstractParameter> Parameters { get; private set; } 

    private Descriptor() { } 
    public Descriptor GetByName(string Name) { ... } 

    private class NestedParameter : AbstractParameter 
    { 
     public NestedParameter() { /* whatever goes here */ } 
    } 
} 
+0

Esto todavía le permite crear otra subclase de parámetro y crear una instancia a través de eso. Haga el constructor 'protected internal', quizás. –

+1

@Martinho: eres incorrecto. Es un concepto erróneo común que "interno protegido" impide que las clases externas al ensamblaje entren en subclases. Debería considerarlo como protegido O interno, no protegido Y interno, es decir, la clase puede derivarse de cualquier otra clase dentro o fuera del ensamblado, pero solo puede crearse una instancia internamente. – Joe

1

Puede usar un constructor marcado Internal.

De esta manera es público para las clases en el ensamblado y privado para las clases fuera de él.

4

LBushkin tiene la idea correcta. Si desea evitar tener que volver a escribir todas las propiedades, haga clic con el botón derecho en el nombre de la clase y seleccione "Refactorizar"> "Extraer interfaz", que le proporcionará una interfaz que contiene todas esas propiedades. (Esto funciona en VS 2008, no sé sobre versiones anteriores.)

C# generalmente tiene el enfoque de que en lugar de evitar el código redundante, VS simplemente lo ayudará a escribirlo más rápido.

0

Hay otra manera: verifique la pila de llamadas para el tipo de llamada.

+3

Los controles de tiempo de ejecución como este son generalmente desaconsejables, tanto porque son caros (debido a la reflexión) como frágiles, si se agregan nuevas clases derivadas que también deberían poder crear una instancia del tipo. – LBushkin

+1

Son frágiles por otras razones también: ¿método que incluye a alguien? –

+0

Los ofuscadores también rompen esto. –

-1

Si solo desea que la clase Descriptor cree una instancia de un parámetro, puede hacer que la clase Descriptor sea una clase de parámetro anidada. (NO al revés) Esto es contrario a la intuición ya que el contenedor o la clase principal es la clase anidada.

public class Parameter 
{ 
    private Parameter() { } 
    public string Name { get; private set; } 
    public string Value { get; private set; } 
    public static Parameter.Descriptor GetDescriptorByName(string Name) 
    { 
    return Parameter.Descriptor.GetByName(Name); 
    } 
    public class Descriptor 
    { // Only class with access to private Parameter constructor 
    private Descriptor() { // Initialize Parameters } 
    public IList<Parameter> Parameters { get; private set; } // Set to ReadOnlyCollection 
    public string Name { get; private set; } 
    public static Descriptor GetByName(string Name) { // Magic here, caching, loading, parsing, etc. } 
    } 
} 
0

marcar la clase de ser "protegido" de instanciación (parámetro) con el StrongNameIdentityPermission attribute y la SecurityAction.LinkDemand option:

[StrongNameIdentityPermission(SecurityAction.LinkDemand, PublicKey="...")] 
class Parameter 
{ 
    ... 
} 

Usted tendrá que proporcionar la clave pública apropiada. Como exige una comprobación del tiempo de enlace (JIT, de hecho) en la clase Parameter, esto significa que solo se puede utilizar desde un ensamblado que está firmado con un nombre fuerte que utiliza la clave privada que coincide con la clave pública que usted suministra en el constructor de atributos anterior. Por supuesto, tendrá que poner la clase Descriptor en un ensamblaje separado y darle un nombre fuerte en consecuencia.

He usado esta técnica en un par de aplicaciones y funcionó muy bien.

Espero que esto ayude.

Cuestiones relacionadas