2009-07-16 2 views
7

Абстрактная функция должна быть реализована всеми конкретными классами.Как заставить все производные классы реализовать абстрактный метод или свойство?

Иногда вы хотите заставить все производные классы реализовать абстрактную функцию, даже производные от конкретных классов.

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

Я хотел бы компилятор сказать программисту, что класс Custom необходимо реализовать Clone(). Есть ли способ?

+0

Пока какой-то конкретный класс в дереве наследования реализован абстрактный метод, почему бы вы заботитесь, если ребенок этого конкретного класса зависит от реализации его родителями? –

+0

Я опаздываю на вечеринку, но у меня такая же проблема.Мне бы очень хотелось иметь классификатор «private abstract», который заставляет каждый производный класс реализовывать функцию, но не позволяет коду classe неявно возвращаться к реализации родительского класса. «Клон() - хороший пример. Мой случай больше похож на «ToString()». Что-то общее, но отличное для каждого класса. –

+0

На самом деле это должно происходить чаще (или мне что-то не хватает). –

ответ

12

Невозможно, чтобы компилятор соблюдал это. Вы можете посмотреть свой собственный плагин анализа на Gendarme или FxCop для обеспечения соблюдения таких требований.

+0

Как бы плагин отличал случай, когда вы хотите заставить требование для всех производных или только непосредственных? –

+0

Атрибут был бы самым простым способом - [MustImplement] или что-то подобное. –

2

Вы должны будете сделать Concrete абстрактным классом, чтобы обеспечить его соблюдение.

+0

Цель состоит в том, чтобы иметь базовый класс, который легко расширить. У вас нет контроля над тем, как люди будут расширять ваш класс. Вы даже не можете быть рядом. Первый приз - задокументировать требование в коде, которое может обеспечить компилятор. –

0

Вы можете проверить это во время выполнения с помощью отражения и выставить исключение, чтобы нарушить выполнение, разрушив хаос для «невежливых» пользователей вашей библиотеки. Эффективность, которая не очень мудрая, даже если вы можете хранить статический HashSet <System.Type> в базовом абстрактном классе со всеми проверенными типами.

Я думаю, что лучше всего предоставить четкую документацию, которая сообщает любому пользователю вашего кода, что считается необходимым переопределить метод Clone().

3

Я бы предположил, что вам действительно не нужны ВСЕ производные классы для реализации абстрактного метода, но это определенно звучит так, будто у вас немного запаха кода в вашем дизайне.

Если у вас нет какой-либо функциональности в методе Concrete.Clone(), вы также можете сделать свой абстрактный класс «Бетон» (просто обязательно измените имя ;-). Оставьте любую ссылку на метод Clone().

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

Если у вас есть некоторые базовые функции в методе Concrete.Clone(), но нужна подробная информация с более высокого уровня, а затем разбить его в собственный абстрактный метод или свойство форсирования более высокого уровня реализации, чтобы поставить этот Информация.

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

Я установил свой пример методы клонирования для возврата базы. Когда вы реализуете клонирование объектов с использованием виртуальных методов Clone, вам нужно * все * производные классы реализовать абстрактный метод. –

-2

реализация удалить в concreate класса или использование базового класса

0

Я сделал следующий тест NUnit, который использует отражение для проверки реализации. Надеюсь, вы сможете адаптироваться по мере необходимости.

Я подозреваю, что он не будет обрабатывать перегруженные методы, но этого достаточно для того, что я хочу.

(Комментарии приветствуются)

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

Он использует следующие методы расширения

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; 
    } 
Смежные вопросы