¿Cómo puedo obligar a todas las clases derivadas a implementar un método o propiedad abstracta?

StackOverflow https://stackoverflow.com/questions/1137677

  •  16-09-2019
  •  | 
  •  

Pregunta

Todas las clases concretas deben implementar una función abstracta.

A veces desea obligar a todas las clases derivadas a implementar la función abstracta, incluso derivadas de clases concretas.

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

Me gustaría que el compilador le diga al programador que la clase Custom necesita implementar Clone(). ¿Hay camino?

¿Fue útil?

Solución

No es posible que el compilador haga cumplir esto. Puede buscar escribir su propio complemento de análisis para Gendarme o Fxcop Para hacer cumplir tales requisitos.

Otros consejos

Supongo que realmente no necesita todas las clases derivadas para implementar el método abstracto, pero definitivamente parece que tiene un olor a un poco de código en su diseño.

Si no tiene ninguna funcionalidad en el método Concrete.Clone (), también puede hacer que su clase 'concreta' resumida (solo asegúrese de cambiar el nombre ;-). Deje de lado cualquier referencia del método Clone ().

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

Si tiene alguna funcionalidad básica en el método Concrete.Clone (), pero necesita información detallada de un nivel superior, entonces divídala en su propio método abstracto o propiedad que obliga a una implementación de nivel superior para proporcionar esta información.

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

Tendrás que hacer Concrete Una clase abstracta para hacer cumplir eso.

Puede verificar esto en el tiempo de ejecución usando la reflexión y lanzar una excepción para interrumpir la ejecución, destruyendo los havoc para los usuarios de "incompletar" de su biblioteca. En cuanto al rendimiento, eso no es muy sabio, aunque podría almacenar una estática Hashsetu003CSystem.Type> en la clase de resumen base con todos los tipos verificados.

Creo que su mejor opción es proporcionar documentación clara que le indique a cualquier usuario de su código que se considera necesario anular el método Clone ().

He realizado la siguiente prueba de NUNIT que usa la reflexión para verificar la implementación. Ojalá pueda adaptarse según sea necesario.

Sospecho que no manejará bien los métodos sobrecargados, pero es suficiente para lo que quiero.

(Comentarios bienvenidos)

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

Utiliza los siguientes métodos de extensión

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

Eliminar la implementación en la clase de concreate o usar la clase base

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top