2009-07-16 5 views
7

Eine abstrakte Funktion muss von allen konkreten Klassen implementiert werden.Wie kann ich alle abgeleiteten Klassen zwingen, eine abstrakte Methode oder Eigenschaft zu implementieren?

Manchmal möchten Sie alle abgeleiteten Klassen zwingen, die abstrakte Funktion zu implementieren, sogar Ableitungen konkreter Klassen.

class Base { protected abstract Base Clone(); } 
class Concrete : Base { protected override Base Clone(){...}; } 
class Custom : Concrete {} 

Ich würde die Compiler wie dem Programmierer zu sagen, dass die Klasse CustomClone() implementieren muss. Gibt es einen Weg?

+0

Solange einige konkrete Klasse in der Vererbungsbaum hat die abstrakte Methode implementiert, warum sollten Sie, wenn ein Kind kümmern von dieser konkreten Klasse hängt von der Umsetzung ihrer Eltern ab? –

+0

Ich bin zu spät zur Party, aber ich habe das gleiche Problem.Ich hätte gerne ein 'private abstract'-Qualifikationsmerkmal, das jede abgeleitete Klasse dazu zwingt, die Funktion zu implementieren, aber verhindert, dass der Code der Klasse implizit auf die Implementierung einer Elternklasse zurückfällt. 'Clone()' ist ein gutes Beispiel. Mein Fall ist mehr wie 'ToString()'. Etwas Generisches, aber verschieden pro Klasse. –

+0

Eigentlich muss das öfter vorkommen (oder mir fehlt etwas). –

Antwort

12

Es ist nicht möglich, dass der Compiler dies erzwingt. Sie können Ihr eigenes Analyse-Plugin unter Gendarme oder FxCop schreiben, um solche Anforderungen durchzusetzen.

+0

Wie würde das Plugin zwischen dem Fall unterscheiden, in dem die Anforderung für alle Derivate erzwungen werden soll, oder nur den unmittelbaren? –

+0

Ein Attribut wäre der einfachste Ansatz - [MustImplement] oder etwas ähnliches. –

2

Sie müssen Concrete eine abstrakte Klasse machen, um das zu erzwingen.

+0

Ziel ist es, eine Basisklasse zu haben, die einfach zu erweitern ist. Sie haben keine Kontrolle darüber, wie die Leute Ihre Klasse erweitern werden. Du bist vielleicht nicht einmal in der Nähe. Der erste Preis besteht darin, die Anforderung im Code zu dokumentieren, die der Compiler durchsetzen kann. –

0

Sie können dies zur Laufzeit mit Reflektion überprüfen und eine Ausnahme auslösen, um die Ausführung zu unterbrechen und damit "unhöfliche" Benutzer Ihrer Bibliothek zu ruinieren. Leistungsmäßig ist das nicht sehr sinnvoll, auch wenn Sie in der abstrakten Basisklasse mit allen verifizierten Typen einen statischen HashSet <System.Type> speichern könnten.

Ich denke, Ihre beste Wette ist es, eine klare Dokumentation bereitzustellen, die jedem Benutzer Ihres Codes sagt, dass es für notwendig erachtet wird, die Clone() Methode zu überschreiben.

3

Ich würde vermuten, dass Sie nicht wirklich alle abgeleiteten Klassen benötigen, um die abstrakte Methode zu implementieren, aber es klingt definitiv so, als hätten Sie ein bisschen Code-Geruch in Ihrem Design.

Wenn Sie keine Funktionalität in der Concrete.Clone() Methode haben, dann können Sie Ihre 'Concrete' Klasse auch abstrakt machen (achten Sie darauf, den Namen zu ändern ;-). Lassen Sie keinen Verweis auf die Clone() -Methode.

abstract class Base { protected abstract void Clone(); } 
abstract class Concrete : Base { } 
class Custom : Concrete { protected override void Clone() { /* do something */ } } 

Wenn Sie einige grundlegende Funktionen in der Concrete.Clone() -Methode, aber benötigen detaillierte Informationen von einer höheren Ebene, dann brechen in seine eigene abstrakte Methode oder Eigenschaft eine höhere Umsetzung zwingen, dies zu liefern Information.

abstract class Base { protected abstract void Clone(); } 

abstract class ConcreteForDatabases : Base 
{ 
    protected abstract string CopyInsertStatemement {get;} 

    protected override void Clone() 
    { 
     // setup db connection & command objects 
     string sql = CopyInsertStatemement; 
     // process the statement 
     // clean up db objects 
    } 
} 

class CustomBusinessThingy : ConcreteForDatabases 
{ 
    protected override string CopyInsertStatemement {get{return "insert myTable(...) select ... from myTable where ...";}} 
} 
+0

Ich habe meine Beispiel-Clone-Methoden korrigiert, um Base zurückzugeben. Wenn Sie das Klonen von Objekten mit virtuellen Clone-Methoden implementieren, benötigen Sie * all * abgeleitete Klassen, um die abstrakte Methode zu implementieren. –

-2

entfernen Implementierung in concreate Klassen und Basisklasse

0

Ich habe den folgenden NUnit-Test gemacht, die Reflexion, die Umsetzung zu überprüfen verwendet. Hoffentlich können Sie sich nach Bedarf anpassen.

Ich vermute, dass es überladene Methoden nicht gut behandelt, aber es ist ausreichend für das, was ich will.

(Kommentare willkommen)

/// <summary> 
/// Use on a (possibly abstract) method or property to indicate that all subclasses must provide their own implementation. 
/// 
/// This is stronger than just abstract, as when you have 
/// 
/// A { public abstract void Method()} 
/// B: A { public override void Method(){} } 
/// C: B {} 
/// 
/// C will be marked as an error 
/// </summary> 
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] 
public class AllSubclassesMustOverrideAttribute : Attribute 
{ 

} 

[TestFixture] 
public class AllSubclassesMustOverrideAttributeTest 
{ 
    [Test] 
    public void SubclassesOverride() 
    { 
     var failingClasses = new List<string>(); 

     foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) 
     { 
      try 
      { 
       foreach (var type in assembly.GetTypes()) 
       { 
        foreach (var methodInfo in type.GetMethods().Where(m => m.HasAttributeOfType<AllSubclassesMustOverrideAttribute>())) 
        { 
         foreach (var subClass in type.ThisTypeAndSubClasses()) 
         { 
          var subclassMethod = subClass.GetMethod(methodInfo.Name); 

          if (subclassMethod.DeclaringType != subClass) 
          { 
           failingClasses.Add(string.Format("Class {0} has no override for method {1}", subClass.FullName, methodInfo.Name)); 
          } 
         } 
        } 

        foreach (var propertyInfo in type.GetProperties().Where(p => p.HasAttributeOfType<AllSubclassesMustOverrideAttribute>())) 
        { 
         foreach (var subClass in type.ThisTypeAndSubClasses()) 
         { 
          var subclassProperty = subClass.GetProperty(propertyInfo.Name); 

          if (subclassProperty.DeclaringType != subClass) 
          { 
           failingClasses.Add(string.Format("Class {0} has no override for property {1}", subClass.FullName, propertyInfo.Name)); 
          } 
         } 

        } 
       } 
      } 
      catch (ReflectionTypeLoadException) 
      { 
       // This will happen sometimes when running the tests in the NUnit runner. Ignore. 
      } 
     } 

     if (failingClasses.Any()) 
     { 
      Assert.Fail(string.Join("\n", failingClasses)); 
     } 
    } 
} 

Es verwendet die folgenden Erweiterungsmethoden

public static bool HasAttributeOfType<T>(this ICustomAttributeProvider provider) 
    { 
     return provider.GetCustomAttributes(typeof(T), false).Length > 0; 
    } 

    public static IEnumerable<Type> ThisTypeAndSubClasses(this Type startingType) 
    { 
     var types = new List<Type>(); 
     foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) 
     { 
      try 
      { 
       foreach (var type in assembly.GetTypes()) 
       { 
        if (startingType.IsAssignableFrom(type)) 
        { 
         types.Add(type); 
        } 
       } 
      } 
      catch (ReflectionTypeLoadException) 
      { 
       // Some assembly types are unable to be loaded when running as nunit tests. 
       // Move on to the next assembly 
      } 
     } 
     return types; 
    } 
Verwandte Themen