2009-12-18 13 views
9

Mi piacerebbe avere la seguente configurazione:.NET: come creare una classe tale che solo un'altra classe specifica possa istanziarla?

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; } 
} 

L'intera struttura sarà di sola lettura una volta caricato da un file XML. Mi piacerebbe farlo, che solo la classe Descriptor può istanziare un parametro.

Un modo per farlo sarebbe quello di fare un'interfaccia IParameter e poi fare Parameter classe privata nella classe descrittore, ma nell'uso del mondo reale dei parametri avrà diverse proprietà, e mi piacerebbe evitare di ridefinire due volte .

È in qualche modo possibile?

risposta

18

Ne fanno una classe nidificata privata che implementa una particolare interfaccia. Quindi, solo la classe esterna può istanziarla, ma chiunque può consumarla (tramite l'interfaccia). Esempio:

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; } 
    } 
} 

Se davvero necessario evitare l'interfaccia, è possibile creare una classe astratta pubblica che ha tutte le proprietà, ma dichiara un costruttore protetto. È quindi possibile creare una classe nidificata privata che eredita dall'estratto pubblico che può essere creata solo dalla classe esterna e restituire le istanze di essa come tipo di base. Esempio:

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

Ciò consente comunque di creare un'altra sottoclasse di Parametro e di creare un'istanza attraverso quella. Rendere il costruttore "protetto all'interno", forse. –

+1

@ Martin: non sei corretto. È un comune equivoco che "protetto all'interno" impedisce alle classi di uscire dall'assembly dalla sottoclasse. Si dovrebbe pensare ad esso come protetto O interno, non protetto E interno - cioè la classe può essere derivata da qualsiasi altra classe all'interno o all'esterno dell'assieme, ma può essere istanziata solo internamente. – Joe

1

È possibile utilizzare un costruttore contrassegnato come Interno.

In questo modo è pubblico per le classi nell'assembly e privato per le classi al di fuori di esso.

4

LBushkin ha l'idea giusta. Se vuoi evitare di dover ridigitare tutte le proprietà, fai semplicemente clic con il pulsante destro sul nome della classe e scegli "Refactor"> "Estrai interfaccia", che dovrebbe darti un'interfaccia che contenga tutte quelle proprietà. (Funziona in VS 2008, non conosco le versioni precedenti.)

C# generalmente accetta l'approccio che, invece di evitare codice ridondante, VS aiuterà semplicemente a scriverlo più velocemente.

0

C'è un altro modo: controlla lo stack di chiamate per il tipo di chiamata.

+3

I controlli di runtime come questi sono in genere sconsigliati, sia perché sono costosi (a causa della riflessione) che fragili, se vengono aggiunte nuove classi derivate che dovrebbero essere in grado di creare un'istanza del tipo. – LBushkin

+1

Sono fragili anche per altri motivi: metodo che elabora qualcuno? –

+0

Anche gli offuscatori rompono questo. –

-1

Se si desidera solo la classe Descrittore per creare un'istanza di un parametro, è possibile rendere la classe descrittore una classe nidificata di parametro. (NON viceversa) Ciò è controintuitivo in quanto il contenitore o la classe genitore è la classe annidata.

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

Mark classe per essere "protetto" da istanziazione (parametro) con la StrongNameIdentityPermission attribute e la SecurityAction.LinkDemand option:

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

Sarà necessario fornire la chiave pubblica appropriata. Poiché si richiede un tempo di collegamento (JIT-time, infatti) di controllo sulla classe Parameter, ciò significa che può essere utilizzato solo da un assembly firmato con un nome sicuro che utilizza la chiave privata che corrisponde alla chiave pubblica che fornisci nel costruttore di attributi sopra. Ovviamente, sarà necessario inserire la classe Descriptor in un assembly separato e assegnargli un nome sicuro.

Ho usato questa tecnica in un paio di applicazioni e ha funzionato molto bene.

Spero che questo aiuti.

Problemi correlati