2009-12-18 10 views
9

Ich hätte gerne folgendes Setup:.NET - Wie man eine Klasse so macht, dass nur eine andere spezifische Klasse sie instanziieren kann?

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

Die gesamte Struktur wird nach dem Laden aus einer XML-Datei schreibgeschützt. Ich möchte es so machen, dass nur die Descriptor-Klasse einen Parameter instanziieren kann.

Eine Möglichkeit wäre, eine IParameter-Schnittstelle zu erstellen und dann die Parameter-Klasse in der Descriptor-Klasse privat zu machen, aber in der Praxis wird der Parameter mehrere Eigenschaften haben, und ich möchte vermeiden, sie zweimal neu zu definieren .

Ist das irgendwie möglich?

Antwort

18

Machen Sie es zu einer privaten geschachtelten Klasse, die eine bestimmte Schnittstelle implementiert. Dann kann nur die äußere Klasse es instanziieren, aber jeder kann es konsumieren (über die Schnittstelle). Beispiel:

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

Wenn Sie wirklich müssen die Schnittstelle vermeiden, können Sie eine öffentliche abstrakte Klasse erstellen, die alle Eigenschaften hat aber erklärt, einen geschützten Konstruktor. Sie können dann eine private verschachtelte Klasse erstellen, die von der öffentlichen Zusammenfassung erbt, die nur von der äußeren Klasse erstellt werden kann, und Instanzen davon als Basistyp zurückgeben. Beispiel:

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

Dadurch können Sie immer noch eine andere Unterklasse von Parameter erstellen und instanziieren. Machen Sie vielleicht den Konstruktor "Geschützt intern". –

+1

@Martinho: Sie sind falsch. Es ist ein häufiges Missverständnis, dass "protected internal" verhindert, dass Klassen außerhalb der Assembly von Unterklassen gebildet werden. Sie sollten es als geschützt ODER intern, nicht geschützt AND intern betrachten - d. H. Die Klasse kann von jeder anderen Klasse innerhalb oder außerhalb der Assembly abgeleitet werden, kann aber nur intern instanziiert werden. – Joe

1

Sie könnten einen Konstruktor mit der Bezeichnung Intern verwenden.

So ist es öffentlich für Klassen in der Assembly und privat für Klassen außerhalb davon.

4

LBushkin hat die richtige Idee. Wenn Sie nicht alle Eigenschaften erneut eingeben müssen, klicken Sie mit der rechten Maustaste auf den Namen der Klasse und wählen Sie "Refactor"> "Interface extrahieren". Dies sollte Ihnen eine Oberfläche geben, die all diese Eigenschaften enthält. (Dies funktioniert in VS 2008. Ich weiß nichts über frühere Versionen.)

C# nimmt im Allgemeinen den Ansatz, dass VS nicht nur überflüssigen Code vermeidet, sondern Ihnen auch hilft, schneller zu schreiben.

0

Es gibt einen anderen Weg: Überprüfen Sie den Call-Stack für den aufrufenden Typ.

+3

Laufzeitprüfungen wie diese werden normalerweise nicht empfohlen - sowohl, weil sie teuer sind (aufgrund von Reflektion) als auch zerbrechlich, wenn neue abgeleitete Klassen hinzugefügt werden, die auch in der Lage sein sollten, den Typ zu instanziieren. – LBushkin

+1

Sie sind auch aus anderen Gründen zerbrechlich: Methode, die irgendjemanden in Ordnung bringt? –

+0

Obfuscators brechen dies auch. –

-1

Wenn Sie möchten, dass nur die Descriptor-Klasse einen Parameter instanziiert, können Sie die Descriptor-Klasse zu einer verschachtelten Klasse von Parameter machen. (NICHT umgekehrt) Dies ist nicht intuitiv, da der Container oder die Elternklasse die geschachtelte Klasse ist.

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

Markieren Sie die Klasse „geschützt“ von Instanziierung (Parameter), um mit dem StrongNameIdentityPermission attribute und der SecurityAction.LinkDemand option:

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

Sie den entsprechenden öffentlichen Schlüssel zur Verfügung stellen müssen. Da Sie für die Klasse Parameter eine Überprüfung der Verbindungszeit (JIT-Zeit) fordern, bedeutet dies, dass sie nur von einer Assembly verwendet werden kann, die mit einem starken Namen versehen ist, der den privaten Schlüssel verwendet, der dem öffentlichen Schlüssel entspricht Sie liefern im obigen Attributkonstruktor. Natürlich müssen Sie die Klasse Descriptor in eine separate Baugruppe einfügen und entsprechend benennen.

Ich habe diese Technik in ein paar Anwendungen verwendet und es hat sehr gut funktioniert.

Hoffe, das hilft.

Verwandte Themen