2009-07-16 9 views
7

Une fonction abstraite doit être implémentée par toutes les classes concrètes.Comment puis-je forcer toutes les classes dérivées à implémenter une méthode ou une propriété abstraite?

Parfois, vous voulez forcer toutes les classes dérivées à implémenter la fonction abstraite, même les dérivées de classes concrètes.

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

Je voudrais que le compilateur de dire au programmeur que la classe Custom a besoin de mettre en œuvre Clone(). Est-ce qu'il y a moyen?

+0

Tant que une classe concrète dans l'arbre d'héritage a mis en œuvre la méthode abstraite, pourquoi voudriez-vous si un enfant de cette classe concrète repose sur la mise en œuvre de ses parents? –

+0

Je suis en retard à la fête mais j'ai le même problème.J'aimerais avoir un qualificateur 'private abstract' qui oblige chaque classe dérivée à implémenter la fonction mais empêche le code de classe de retomber implicitement dans l'implémentation d'une classe parente. 'Clone()' est un bon exemple. Mon cas ressemble plus à 'ToString()'. Quelque chose de générique mais distinct par classe. –

+0

En fait, cela doit se produire plus souvent (ou il me manque quelque chose). –

Répondre

12

Il n'est pas possible pour le compilateur de l'appliquer. Vous pourriez envisager d'écrire votre propre plugin d'analyse à Gendarme ou FxCop pour appliquer ces exigences.

+0

Comment le plugin distinguerait le cas où vous voulez forcer l'exigence sur tous les produits dérivés ou seulement les produits immédiats? –

+0

Un attribut serait l'approche la plus simple - [MustImplement] ou quelque chose de similaire. –

2

Vous devrez créer Concrete une classe abstraite pour l'appliquer.

+0

L'objectif est d'avoir une classe de base facile à étendre. Vous n'avez aucun contrôle sur la façon dont les gens vont prolonger votre cours. Vous ne pouvez même pas être là. Le premier prix consiste à documenter l'exigence dans le code que le compilateur peut appliquer. –

0

Vous pouvez vérifier cela au moment de l'exécution en utilisant la réflexion et lancer une exception pour perturber l'exécution, ce qui détruira les utilisateurs "impolis" de votre bibliothèque. Performance-sage ce n'est pas très sage, même si vous pouvez stocker un static HashSet <System.Type> dans la classe abstraite de base avec tous les types vérifiés.

Je pense que votre meilleur pari est de fournir une documentation claire qui indique à tout utilisateur de votre code qu'il est jugé nécessaire de surcharger la méthode Clone().

3

Je suppose que vous n'avez pas vraiment besoin de TOUTES les classes dérivées pour implémenter la méthode abstraite, mais il semble que vous ayez une légère odeur de code dans votre conception.

Si vous ne disposez d'aucune fonctionnalité dans la méthode Concrete.Clone(), vous pouvez également créer votre abstract de classe 'Concrete' (assurez-vous simplement de changer le nom ;-). Ignorez toute référence de la méthode Clone().

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

Si vous avez une fonctionnalité de base dans la méthode Concrete.Clone(), mais qui ont besoin des informations détaillées d'un niveau supérieur, puis casser dehors dans sa propre méthode abstraite ou bien forcer une mise en œuvre de niveau supérieur pour fournir cette 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

J'ai corrigé mon exemple de méthode Clone pour retourner Base. Lorsque vous implémentez le clonage d'objet à l'aide de méthodes de clonage virtuel, vous devez utiliser des classes dérivées * all * pour implémenter la méthode abstraite. –

-2

supprimer la mise en œuvre en classe concreate ou utilisation classe de base

0

J'ai fait le test NUnit suivant qui utilise la réflexion pour vérifier la mise en œuvre. J'espère que vous pourrez vous adapter au besoin.

Je suppose qu'il ne gérera pas bien les méthodes surchargées, mais c'est suffisant pour ce que je veux.

(commentaires sont les bienvenus)

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

Il utilise les méthodes d'extension suivantes

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