Hans Malherbe
Hans Malherbe

Reputation: 3018

How can I force all derived classes to implement an abstract method or property?

An abstract function must be implemented by all concrete classes.

Sometimes you want to force all derivative classes to implement the abstract function, even derivatives of concrete classes.

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

I would like the compiler to tell the programmer that the class Custom needs to implement Clone(). Is there way?

Upvotes: 7

Views: 3739

Answers (6)

Stuart Moore
Stuart Moore

Reputation: 801

I've made the following NUnit test that uses reflection to check the implementation. Hopefully you can adapt as needed.

I suspect it won't handle overloaded methods well, but it's sufficient for what I want.

(Comments welcome)

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

It uses the following extension methods

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

Upvotes: 0

Guna
Guna

Reputation: 1

remove implementation in concreate class or use base class

Upvotes: -2

John MacIntyre
John MacIntyre

Reputation: 13031

I would guess you don't really need ALL derived classes to implement the abstract method, but it definitely sounds like you have a bit of a code smell in your design.

If you don't have any functionality in the Concrete.Clone() method, then you can make your 'Concrete' class abstract as well (just be sure to change the name ;-). Leave out any reference of the Clone() method.

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

If you have some basic functionality in the Concrete.Clone() method, but need detailed information from a higher level, then break it out into it's own abstract method or property forcing a higher level implementation to supply this 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 ...";}}
}

Upvotes: 3

Cecil Has a Name
Cecil Has a Name

Reputation: 4992

You can check this at run-time using reflection and throw an exception to disrupt execution, wrecking havoc for "impolite" users of your library. Performance-wise that is not very wise, even though you could store a static HashSet<System.Type> in the base abstract class with all verified types.

I think your best bet is to provide clear documentation that tells any user of your code that it is deemed necessary to override the Clone() method.

Upvotes: 0

leppie
leppie

Reputation: 117280

You will have to make Concrete an abstract class to enforce that.

Upvotes: 2

Michael Donohue
Michael Donohue

Reputation: 11876

It's not possible for the compiler to enforce this. You might look at writing your own analysis plugin to Gendarme or FxCop to enforce such requirements.

Upvotes: 12

Related Questions