2009-12-09 5 views
33

Wenn ich den folgenden Code Beispiel haben:Wie verberge ich eine geerbte Eigenschaft in einer Klasse, ohne die geerbte Klasse (Basisklasse) zu ändern?

public class ClassBase 
{ 
    public int ID { get; set; } 

    public string Name { get; set; } 
} 

public class ClassA : ClassBase 
{ 
    public int JustNumber { get; set; } 

    public ClassA() 
    { 
     this.ID = 0; 
     this.Name = string.Empty; 
     this.JustNumber = string.Empty; 
    } 
} 

Was soll ich tun Eigentum Name (nicht als Mitglied der KlasseA Mitglieder gezeigt) zu verstecken, ohne ClassBase zu modifizieren?

+1

Dies ist eine Verletzung eines der Grundprinzipien von OOP (Polymorphismus und, verwandt, das 'L' in SOLID). – jason

+1

Speziell eine Verletzung des Liskow-Substitutionsprinzips (http://en.wikipedia.org/wiki/Liskov_substitution_principle) –

+13

Viele der Antworten ignorieren die einfache Tatsache, dass eine Basisklasse von einer dritten Partei bereitgestellt werden kann. Da dieser Code nicht gehört, kann er nicht geändert werden. Es gibt eine definitive Gültigkeit, eine engere Version der Basisklasse durch Ausblenden von Mitgliedern erstellen zu wollen. Und es ist schöner, zu erben, als das Basis-Steuerelement als Komponente eines neuen Steuerelements zu verwenden, da dies viel zusätzliche Arbeit erfordert, um Methoden zu offenbaren, die bereits auf der Basis gefunden wurden. – Mario

Antwort

46

Ich rieche hier einen Code Geruch. Ich bin der Meinung, dass Sie nur eine Basisklasse erben sollten, wenn Sie die gesamte Funktionalität dieser Basisklasse implementieren. Was Sie tun, repräsentiert nicht wirklich objektorientierte Prinzipien. Wenn Sie also von Ihrer Basis erben möchten, sollten Sie Name implementieren, andernfalls haben Sie Ihre Vererbung falsch herum. Ihre Klasse A sollte Ihre Basisklasse sein und Ihre aktuelle Basisklasse sollte von A erben, wenn Sie das wollen und nicht umgekehrt.

Allerdings nicht zu weit von der direkten Frage zu verirren. Wenn Sie haben wollen „die Regeln“ hinwegsetzen und auf dem Weg fortsetzen möchten Sie gewählt haben - hier ist, wie Sie es gehen kann:

Die Konvention ist die Eigenschaft zu implementieren, sondern eine NotImplementedException werfen, wenn diese Eigenschaft heißt - obwohl ich das auch nicht mag. Aber das ist meine persönliche Meinung und ändert nichts daran, dass diese Konvention noch immer besteht.

Wenn Sie veraltete die Eigenschaft versucht sind (und es ist in der Basisklasse als virtuelle deklariert), dann können Sie entweder das Obsolete-Attribut auf sie aus:

[Obsolete("This property has been deprecated and should no longer be used.", true)] 
public override string Name 
{ 
    get 
    { 
     return base.Name; 
    } 
    set 
    { 
     base.Name = value; 
    } 
} 

(Edit: Als Brian In den Kommentaren wird darauf hingewiesen, dass der zweite Parameter des Attributs einen Compilerfehler verursacht, wenn jemand die Name-Eigenschaft referenziert. Daher können sie ihn nicht verwenden, obwohl Sie ihn in der abgeleiteten Klasse implementiert haben.)

Oder wie ich erwähnte NotImplementedException verwenden:

public override string Name 
{ 
    get 
    { 
     throw new NotImplementedException(); 
    } 
    set 
    { 
     throw new NotImplementedException(); 
    } 
} 

Wenn jedoch die Eigenschaft nicht als virtuelle deklariert ist, dann können Sie das neue Schlüsselwort verwenden, um es zu ersetzen:

public new string Name 
{ 
    get 
    { 
     throw new NotImplementedException(); 
    } 
    set 
    { 
     throw new NotImplementedException(); 
    } 
} 

Sie können das Obsolete-Attribut auf die gleiche Weise verwenden, wie Wenn die Methode überschrieben wurde, oder Sie die NotImplementedException auslösen können, je nachdem, was Sie wählen. Ich würde wahrscheinlich verwenden:

[Obsolete("Don't use this", true)] 
public override string Name { get; set; } 

oder:

[Obsolete("Don't use this", true)] 
public new string Name { get; set; } 

Je nachdem, ob es nicht als virtuelle in der Basisklasse deklariert wurde.

+5

Das obsolete Attribut hat auch einen zweiten Parameter, der angibt, dass die Verwendung der Eigenschaft als Fehler betrachtet werden sollte. An diesem Punkt sollten Sie einen Kompilierzeitfehler erhalten, der hilfreich ist. –

+0

Danke Brian, ich hätte denken sollen, dass, guter Fang. – BenAlabaster

+0

Ich glaube, Sie können auch das Schlüsselwort "new" verwenden, um neue Funktionen für die Eigenschaft anzugeben, auch wenn sie nicht als virtuell markiert sind. Dies würde es ihm ermöglichen, die Eigenschaft als obsolet zu markieren, selbst wenn es aus einer Klasse stammt, in der die Eigenschaft nicht virtuell war. –

1

Sie können nicht, das ist der ganze Punkt der Vererbung: die Unterklasse muss alle Methoden und Eigenschaften der Basisklasse bieten.

Sie könnten die Implementierung ändern, um eine Ausnahme zu werfen, wenn die Eigenschaft aufgerufen wird (wenn es virtuell sind) ...

25

Während technisch Eigentum wird nicht ausgeblendet werden, eine Art und Weise stark seine Verwendung zu entmutigen ist Attribute auf sie wie diese setzen:

[Browsable(false)] 
[Bindable(false)] 
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
[EditorBrowsable(EditorBrowsableState.Never)] 

Dies ist, was System.Windows.Forms für Kontrollen tut, die haben Eigenschaften, die nicht passen. Die Text -Eigenschaft, zum Beispiel, ist auf Control, aber es macht nicht Sinn für jede Klasse, die von Control erbt. So in Monthcalendar zum Beispiel scheint es, dies wie (pro Reflektor):

[Browsable(false), Bindable(false), 
    DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), 
    EditorBrowsable(EditorBrowsableState.Never)] 
public override string Text 
{ 
    get { return base.Text; } 
    set { base.Text = value; } 
} 

Browsable zeigt an, ob das Element im Eigenschaftenfenster erscheint. EditorBrowsable zeigt an, ob es im Dropdown-Feld Intellisense angezeigt wird. Sie können die Eigenschaft trotzdem eingeben, wenn EditorBrowsable auf false festgelegt ist und weiterhin erstellt wird, aber es ist nicht so offensichtlich, dass Sie sie verwenden können.

+1

Ich musste 'public new string Text' schreiben, damit es funktioniert. – szamil

19

einfach verstecken

public class ClassBase 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 
} 
public class ClassA : ClassBase 
{ 
    public int JustNumber { get; set; } 
    private new string Name { get { return base.Name; } set { base.Name = value; } } 
    public ClassA() 
    { 
     this.ID = 0; 
     this.Name = string.Empty; 
     this.JustNumber = 0; 
    } 
} 

Hinweis: Name wird immer noch ein öffentliches Mitglied Classbase sein, da die Einschränkung der nicht die Basisklasse zu ändern gibt es keine Möglichkeit, das zu stoppen ist.

+4

Mit dieser Methode wird etwas wie 'ClassA.Name' außerhalb von' ClassA' die Eigenschaft 'ClassBase.Name' verfügbar machen. Es ist nicht möglich, einem Mitglied einen eingeschränkteren Zugang zu "Neu" zu geben, als ursprünglich deklariert wurde. –

+0

Das ist der Punkt der Notiz. Der Name ist kein öffentliches Mitglied von ClassA gemäß der Frage. Für die Serialisierung und Reflektion kann dies wichtig sein. – CCondron

+0

Ja, das stimmt. Es ist jedoch erwähnenswert, dass, wenn man ein Mitglied vor einer geerbten Klasse verbergen möchte, dies daran liegen kann, dass er den Wert des Mitglieds auf einen Satz vordefinierter Werte setzt, und daher kann das Ändern zu einem unerwarteten Verhalten führen. –

4

Warum Erzwingung erzwingen, wenn es nicht notwendig ist? Ich denke, der richtige Weg, es zu tun ist has-a anstelle von is-a.

public class ClassBase 
{ 
    public int ID { get; set; } 

    public string Name { get; set; } 
} 

public class ClassA 
{ 
    private ClassBase _base; 

    public int ID { get { return this._base.ID; } } 

    public string JustNumber { get; set; } 

    public ClassA() 
    { 
     this._base = new ClassBase(); 
     this._base.ID = 0; 
     this._base.Name = string.Empty; 
     this.JustNumber = string.Empty; 
    } 
} 
3

Ich glaube nicht, dass viele Leute, die hier antworten, Vererbung überhaupt verstehen. Es besteht die Notwendigkeit, von einer Basisklasse zu erben und ihre einmal öffentlichen Variablen und Funktionen auszublenden. Beispiel: Nehmen wir an, Sie haben eine Basis-Engine und möchten eine neue Engine erstellen, die überkomprimiert ist. Nun, 99% der Engine, die Sie verwenden werden, aber Sie werden ein wenig von seiner Funktionalität zwicken, um es viel besser laufen zu lassen und dennoch gibt es einige Funktionen, die nur den vorgenommenen Änderungen gezeigt werden sollten, nicht der Endbenutzer. Weil wir alle wissen, dass jede MS-Klasse keine Änderungen benötigt.

Neben der Verwendung der neuen, um einfach die Funktionalität zu überschreiben, ist es eines der Dinge, die Microsoft in ihrer unendlichen wis ... .. oh, ich meine Fehler als ein Tool nicht mehr wert.

Der beste Weg, dies jetzt zu erreichen, ist die Multilevel-Vererbung.
public class classA { } public class B: A {} public class C: B {}

Klasse B macht die ganze Arbeit und Klasse C aussetzt, was Sie ausgesetzt benötigen.

0

Ich denke, es ist schlechtes Design, wenn Sie dies tun müssen, vor allem, wenn Sie in der Lage sind, den Code von Grund auf zu entwerfen.

Warum?

Gutes Design ist es, die Basisklasse gemeinsame Eigenschaften teilen zu lassen, die ein bestimmtes Konzept hat (virtuell oder real). Beispiel: System.IO.Stream in C#.

Weiter unten auf der Spur schlechte Design wird die Kosten für die Wartung erhöhen und machen die Umsetzung schwieriger und härter. Vermeiden Sie so viel wie möglich!

Grundregeln, die ich benutze:

  • Minimieren Sie die Anzahl von Eigenschaften und Methoden in der Basisklasse. Wenn Sie nicht erwarten, einige Eigenschaften oder Methoden in einer Klasse zu verwenden, die die Basisklasse erbt; Setze es dann nicht in die Basisklasse. Wenn Sie sich in der Entwicklungsstufe eines Projekts befinden; gehe immer wieder zurück zum Zeichenbrett und dann zum Design, weil sich die Dinge ändern! Redesign bei Bedarf. Wenn dein Projekt live ist, werden die Kosten für die spätere Änderung der Designs steigen!

    • Wenn Sie eine Basisklasse mit einem 3 implementiert: rd party, betrachten wir eine Ebene „nach oben gehen“ statt „überschreiben“ mit „NotImplementedException“ oder so. Wenn es keine andere Ebene gibt, sollten Sie den Code von Grund auf neu entwerfen.

    • Denken Sie immer daran, Klassen zu versiegeln, die Sie nicht möchten, dass jemand sie erben kann. Es zwingt Coder dazu, in der "Vererbungshierarchie" eine Ebene höher zu gehen und somit "losen Enden" wie "NotImplementedException" zu vermeiden.

0

Ich weiß, dass die Frage ist alt, aber was Sie können die PostFilterProperties wie dies zu tun ist außer Kraft setzen:

protected override void PostFilterProperties(System.Collections.IDictionary properties) 
    { 
     properties.Remove("AccessibleDescription"); 
     properties.Remove("AccessibleName"); 
     properties.Remove("AccessibleRole"); 
     properties.Remove("BackgroundImage"); 
     properties.Remove("BackgroundImageLayout"); 
     properties.Remove("BorderStyle"); 
     properties.Remove("Cursor"); 
     properties.Remove("RightToLeft"); 
     properties.Remove("UseWaitCursor"); 
     properties.Remove("AllowDrop"); 
     properties.Remove("AutoValidate"); 
     properties.Remove("ContextMenuStrip"); 
     properties.Remove("Enabled"); 
     properties.Remove("ImeMode"); 
     //properties.Remove("TabIndex"); // Don't remove this one or the designer will break 
     properties.Remove("TabStop"); 
     //properties.Remove("Visible"); 
     properties.Remove("ApplicationSettings"); 
     properties.Remove("DataBindings"); 
     properties.Remove("Tag"); 
     properties.Remove("GenerateMember"); 
     properties.Remove("Locked"); 
     //properties.Remove("Modifiers"); 
     properties.Remove("CausesValidation"); 
     properties.Remove("Anchor"); 
     properties.Remove("AutoSize"); 
     properties.Remove("AutoSizeMode"); 
     //properties.Remove("Location"); 
     properties.Remove("Dock"); 
     properties.Remove("Margin"); 
     properties.Remove("MaximumSize"); 
     properties.Remove("MinimumSize"); 
     properties.Remove("Padding"); 
     //properties.Remove("Size"); 
     properties.Remove("DockPadding"); 
     properties.Remove("AutoScrollMargin"); 
     properties.Remove("AutoScrollMinSize"); 
     properties.Remove("AutoScroll"); 
     properties.Remove("ForeColor"); 
     //properties.Remove("BackColor"); 
     properties.Remove("Text"); 
     //properties.Remove("Font"); 
    } 
+0

Ich denke, dass dieser Code Winforms oder ASP Xamarin Formen spezifisch ist. Erwähnen Sie es in Ihrer Antwort – Kira

2

Ich stimme völlig, dass die Eigenschaften nicht von Basisklassen entfernt werden sollte, aber manchmal kann eine abgeleitete Klasse eine andere, geeignetere Art haben, die Werte einzugeben. In meinem Fall zum Beispiel erben wir von ItemsControl. Wie wir alle wissen, besitzt ItemsControl die ItemsSource-Eigenschaft, aber ich möchte, dass mein Steuerelement Daten aus zwei Quellen zusammenfügt (z. B. Person und Standort). Wenn der Benutzer die Daten mithilfe von ItemsSource eingeben soll, muss ich die Werte trennen und neu kombinieren. Daher habe ich 2 Eigenschaften erstellt, um die Daten einzugeben. Aber zurück zur ursprünglichen Frage bleibt die ItemsSource, die ich nicht verwenden soll, weil ich sie mit meinen eigenen Eigenschaften "ersetze". Ich mag die Browsable- und EditorBrowsable-Ideen, aber es hindert den Benutzer dennoch nicht daran, sie zu verwenden. Der grundlegende Punkt hier ist, dass die Vererbung die meisten Eigenschaften behalten sollte, aber wenn es eine große komplexe Klasse gibt (besonders solche, bei denen Sie den ursprünglichen Code nicht ändern können), wäre das Umschreiben alles sehr ineffizient.