2013-07-02 14 views
8

Ich habe so etwas wie dieses bekam:Kann ich anderen Baugruppen verbieten, von einer Klasse zu erben?

// This gets implemented by plugin authors to get callbacks about various things. 
public interface ExternalPlugin 
{ 
    // This gets called by the main application to tell the plugin some data 
    // is available or similar. 
    void DoStuff(SomeDataBlob blob); 
} 

// Data blob for v1 of API 
public class SomeDataBlob 
{ 
    internal SomeDataBlob(string prop) { Prop = prop; } 
    // Some piece of data that V1 plugins need 
    public string Prop { get; private set; } 
} 

// FUTURE! 
// Data blob API v2 of API 
public class SomeDataBlobV2 : SomeDataBlob 
{ 
    // Can be passed to clients expecting SomeDataBlob no problem. 
    internal SomeDataBlobV2(string prop, string prop2) :base(prop) { Prop2 = prop2; } 
    // Some piece of data that V2 plugins need. V2 plugins can cast to this from 
    // SomeDataBlob, but still can load successfully into older versions that support 
    // only V1 of the API 
    public string Prop2 { get; private set; } 
} 

Ich habe SomeDataBlob öffentlich zu machen, so dass sie als Mitglied der öffentlichen Schnittstelle Methode ExternalPlugin.DoStuff verwendet werden kann. Ich möchte jedoch nicht zulassen, dass Clients von dieser Klasse erben und somit anfällig für das Problem der spröden Basisklasse sind. (Alle Derivate dieser Klasse sollten in derselben Baugruppe aufbewahrt werden.)

Das Markieren der Klasse sealed geht zu weit, weil ich glaube, dass das Entfernen von sealed eine kaputte API-Änderung ist; und selbst wenn das nicht ist, kann ich die SomeDataBlobV2 Clients immer noch die falsche Sache tun und von SomeDataBlob direkt erben.

Gibt es eine Möglichkeit, diese Art von Muster durchzusetzen?

+0

machen Sie es nur intern ... zuvor hier beantwortet ... http://StackOverflow.com/Questions/1223873/namespace-only-Class-visibility-in-C-net – phillip

+4

Schnittstellen. Schnittstellen für den Sieg. –

+0

@Guru: Dann können Clients, die "V2" der API verwenden, nicht feststellen, ob sie die neuen Funktionen von "V2" verwenden können. –

Antwort

3
/// <summary> 
    /// This is a dummy constructor - it is just here to prevent classes in other assemblies 
    /// from being derived from this class. 
    /// See http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2971840&SiteID=1 
    /// </summary> 
    internal MhlAdminLayer() { } 

Die Idee ist, einen Konstruktor ohne interne Parameter zu haben. Dann kann die Klasse nicht abgeleitet werden.

Bearbeiten: Entschuldigung, der Link im Kommentar funktioniert nicht mehr.

Edit 2: http://msdn.microsoft.com/en-us/library/vstudio/ms173115.aspx

„Du eine Klasse verhindern kann aus privaten, indem der Konstruktor instanziiert wird ...“

+0

Die Klasse, die er in seiner Frage zeigt, hat bereits alle Instanz-Konstruktoren "intern", also könnte er sein "Problem" bereits gelöst haben. –

+0

Ich glaube, der Trick hängt davon ab, dass es sich um einen parameterlosen Konstruktor handelt. Leider kann ich mich jetzt nicht an die Details erinnern, dachte nur, dass es ein netter Trick war, als ich es vor einigen Jahren auf MSDN entdeckte - und jetzt haben sie diesen Thread oder etwas entfernt. – RenniePet

+0

Nun, wenn eine Klasse von einer anderen Klasse abgeleitet ist, müssen einige ihrer Instanzkonstruktoren einen Instanzkonstruktor der Basisklasse "verketten" (es gibt ein pathologisches Gegenbeispiel, das ich nicht erwähnen werde). Wenn Sie sicherstellen, dass _all_ Instanzkonstruktoren der anderen Klasse "internal" oder "private" sind, kann keine Klasse in einer fremden Assembly auf sinnvolle Weise erben. Wenn im Quellcode einer (nicht statischen) Klasse überhaupt keine Instanzkonstruktoren angezeigt werden, erstellt der Compiler einen Standardkonstruktor für Sie, auf den von außen zugegriffen werden kann, sodass Sie dies vermeiden möchten. –

9

Machen Sie die Klasse intern und geben Sie stattdessen eine Schnittstelle frei. Verwenden Sie dann das Factory-Muster, um die korrekte Implementierung zu erstellen.

public interface ISomeDataBlob 
{ 
} 

internal class SomeDataBlob : ISomeDataBlob 
{ 
} 

public class BlobApiFactory 
{ 
    ISomeDataBlob Create(); 
} 

Sie verbergen die Implementierung, aber geben Sie dem Benutzer immer noch Zugriff auf alles.)

Edit (Antwort auf einen Kommentar aus dem OP)

What I effectively want is some method taking some parameters. I want to be able to add parameters that the main application can provide in a future version if the API without breaking clients. But I don't want clients to be able to create instances of the "parameters" class/interface or otherwise interact with it beyond receiving an instance of it as a parameter

Statt die APIs verstecken können Sie sicherstellen, dass alle Objekt Ihrer Bibliothek übergeben, Sie auch Unit-Tests einfacher für die Benutzer machen stammt aus der Assembly:

public class YourCoolService 
{ 
    public void DoSomething(ISomeDataBlob blob) 
    { 
     if (blob.GetType().Assembly != Assembly.GetExecutingAssembly()) 
      throw new InvalidOperationException("We only support our own types"); 
    } 
} 

EDIT2

Gerade bemerkt, dass @RQDQ diese Lösung bereits zur Verfügung gestellt (bei der Beantwortung Ihres Kommentars nicht bemerkt). Wenn das die Lösung ist, die Sie wollen, akzeptieren Sie stattdessen seine Antwort.

+0

Dies hat jedoch das gleiche Problem. Die externe Assembly könnte ISomeDataBlob implementieren, und ich möchte das nicht zulassen. –

+6

Wenn Sie den Vertrag nicht einmal veröffentlichen möchten, wie würden die Benutzer Ihre API jemals nutzen können? – jgauffin

+0

Was ich effektiv möchte, ist eine Methode, die einige Parameter nimmt. Ich möchte in der Lage sein, Parameter hinzuzufügen, die die Hauptanwendung in einer zukünftigen Version bereitstellen kann, wenn die API ohne Clients zu brechen. Aber ich möchte nicht, dass Clients in der Lage sind, Instanzen der "Parameter" -Klasse/Schnittstelle zu erzeugen oder anderweitig mit ihr zu interagieren, anstatt eine Instanz davon als Parameter zu erhalten. –

1

Soweit ich weiß, Schnittstellen sind die Art und Weise, dies zu tun. Es wäre eine Änderung der API, aber es würde bedeuten, dass Sie tun könnten, was Sie wollen.

public interface ExternalPlugin 
{ 
    void DoStuff(ISomeDataBlob blob); 
} 
// Interfaces: 
public interface IDataBlob 
{ 
    string Prop { get; } 
} 
public interface IDataBlobV2 : IDataBlob 
{ 
    string Prop2 { get; } 
} 
// Data blob for v1 of API 
internal class SomeDataBlob : IDataBlob 
{ 
    internal SomeDataBlob(string prop) { Prop = prop; } 
    public string Prop { get; private set; } 
} 
// FUTURE! 
// Data blob API v2 of API 
public class SomeDataBlobV2 : SomeDataBlob, IDataBlobV2 
{ 
    // Can be passed to clients expecting SomeDataBlob no problem. 
    internal SomeDataBlobV2(string prop, string prop2) : base(prop) { Prop2 = prop2; } 
    public string Prop2 { get; private set; } 
} 

Und dann, um die Objekte das Fabrikmuster, z.

2

Wenn Sie auf der Suche nach versiegelten UND weiterhin mit Klassen sind, können Sie dies zur Laufzeit erzwingen. Mit anderen Worten: Untersuchen Sie die beteiligten Klassen an Ihrer API-Grenze und stellen Sie sicher, dass sie von Ihrer Assembly stammen.

Ein einfaches Beispiel:

public void Bar(Foo foo) 
{ 
    if (foo.GetType().Assembly != this.GetType().Assembly) 
     throw new InvalidOperationException("It's not one of mine!"); 
} 

public class Foo 
{ 
} 
+0

Es gibt vollkommen gute Möglichkeiten, dies zur Kompilierzeit zu verhindern ... – Yaur

0

Was würde ich tun, ist eine Art von Factory-Klasse machen, die eine Schnittstelle macht, die eine Instanz von was auch immer Version für die spezifischen API Ihr Client verwendet, und versteckt das passieren würde Umsetzung mit einer internen Klasse

Sie können auch Einschränkungen verwenden es wenig leichter zu bedienen, dann kann der Client nur setzen die Art des Gegenstandes sie suchen

public interface IBlobV1 { /*public stuff for V1 here*/ } 
internal class BlobV1: IBlobV1 {/*v1 implementation*/ } 

public interface IBlobV2 : IBlobV1 {/*public interface for V2 here*/ } 
internal class BlobV2:BlobV1,IBlobV2 {/*v2 implementation*/} 



public sealed BlobFactory 
{ 
    public IBlobV1 CreateVersion1Blob(){/* implementation */} 
    public IBlobV2 CreateVersion2Blob(){/* implementation */} 
    public T CreateBlob<T>() 
      where T: IBlobV1 
    { /* implementation */} 
} 
0

SomeDataBlob kann nicht vererbt werden, da sein einziger Konstruktor intern ist. Wenn Sie versuchen, eine abgeleitete Klasse in einer Client-Anwendung zu implementieren:

class SomeDataBlobClient : SomeDataBlob 
{ 
    SomeDataBlobClient():base("TEST") 
    { 

    } 
} 

Sie erhalten die folgende Fehlermeldung erhalten:

The type 'ClassLibrary1.SomeDataBlob' has no constructors defined

Es scheint, dass Sie Ihr eigenes Problem gelöst.

Verwandte Themen