7

So why isn't it allowed to have Shared MustOverride/Overridable members? Some argue that overriding is related to inheritance, which doesn't make sense in case of Shared members because there is no instantiation involved. Here is one example where I need it:

My base class DrawingObject defines a Shared member named TypeName that must be implemented by each child class to return a unique identification string, which is different for each child type, but same for all instance of one child type. Now this requires me to have TypeName property defined as Shared and Overridable. Or is there a better way of doing it?

Base class

Public MustInherit Class DrawingObject
    Public MustOverride ReadOnly Property TypeName As String
End Class

Child class

Public Class Rectangle
    Inherits DrawingObject

  Public Overrides ReadOnly Property TypeName As String
    Get
      Return A_CONST_STRING_DEFINED_IN_THIS_CLASS
    End Get
  End Property
End Class

This code works fine, but ideally TypeName should have been Shared since it returns a Const.

dotNET
  • 33,414
  • 24
  • 162
  • 251
  • if you don't mind could you please post the code – sujith karivelil Sep 08 '15 at 04:45
  • @un-lucky: Posted some code. Plz take a look. – dotNET Sep 08 '15 at 04:50
  • Making a property shared and readonly still doesn't enforce that the inheritor returns a *constant* value, so it still doesn't seem to solve the problem you want to. More probably, it sounds like you need to use reflection and an attribute, but there's no way to force the inheritor to apply the attribute either. – Damien_The_Unbeliever Sep 08 '15 at 06:32

2 Answers2

4

The whole point of overriding is to facilitate polymorphism. You pass an object around and the way it behaves depends on the type of the object rather than the type of the reference. If you're calling Shared members then you're calling them on a type rather than an object so polymorphism doesn't apply so overriding offers no advantage.

In your case, if you want to get the TypeName of an object without knowing what type that object is at run time then overriding that property would make sense. No matter where you are, you can get that property and you'll get the name of the type of that object. With a Shared member, you're going to be getting the property on a specific type so you can simply get a property of that type.

Requested Example:

Let's say that you have a shapes that know how to draw themselves on screen. You might start with a base Shape class with a base Shape class with a Draw method and then inherit that class in, for example, Square and Circle classes. You could then have a method like this:

Public Sub DrawShape(myShape As Shape)
    myShape.Draw()
End Sub

In that case it makes sense to override the Draw method in the derived classes because doing so allows you to simply call Draw wherever you have a Shape reference and know that it will be drawn correctly. If that method is passed a Square then a square will be drawn and if it is passed a Circle then a circle would be drawn, but the method doesn't have to know or care, thanks to polymorphism.

If what you are suggesting was possible though, and Draw was a Shared method, you would have to call Square.Draw every time you wanted to draw a square and Circle.Draw every time you wanted to draw a circle. The whole point of overriding is that you're supposed to be able to call the method on a reference of the base type and get functionality defined in the derived type. In your scenario, you'd have to call the method on the derived type to get functionality defined in the derived type, so you get no advantage. You couldn't just call Shape.Draw and have anything useful happen. Apart from anything else, which derived class would it choose?

jmcilhinney
  • 50,448
  • 5
  • 26
  • 46
  • I'm having a hard time understanding your point here. Can you plz post some sample code with it? – dotNET Sep 22 '15 at 05:16
  • 2
    Thanks for the explanation, but you're just describing object polymorphism here. My actual problem remains unsolved. What is the proper way of forcing derived classes to expose a shared member? We can do this using `MustOverride` for non-shared members, but there doesn't appear to be any such way for shared members. The problem statement here is simple: If a child class is a `DrawingObject`, it does have a `TypeName`, which incidentally is common for all child class objects (hence `Shared`). Now there should be a way for base class to force it on its children. – dotNET Sep 22 '15 at 06:35
  • Also, I'm not contesting whether this is a type of polymorphism or not. I'm just searching for a way of doing it. – dotNET Sep 22 '15 at 06:37
  • There is no way. As I have stated, overriding doesn't make sense for a `Shared` member. I can see how being able to enforce implementation of a member in a derived type might be handy but they're not going to provide the ability to override just for that. Something like `MustShadow` would be appropriate but that doesn't exist so what you want cannot be done. – jmcilhinney Sep 22 '15 at 06:57
  • I think it makes sense a bit. Say you want circle and boxes to have the exact same function called draw. That means it must be overidable. What about if the function you want is type. – user4951 Nov 10 '17 at 17:06
  • @J.Chang, no it doesn't make sense a bit. What you're suggest only makes sense if it's an instance method. If it was a `Shared` method then you'd have to call it on the specific type anyway, so it's being overridden is of no value. You could just declared separate methods. Overriding is only of use if you can pass an instance of the derived class around via a reference of the base type and still invoke the derived implementation of the method. With `Shared` methods you have to call them on the type so how could you invoke a derived implementation via the base type? – jmcilhinney Nov 10 '17 at 23:32
  • This is not true. Imagine you have an abstract class and want all derived classes to implemented a certain shared function, yet differently. In my example I need to deserialize a string using newtonsoft, calling newtonsoft with the generic argument of the class type, without using generics in the shared function (the class is to be determined from a database string and without the use of 100 select - case everywhere). I solved the issue with a newtonsoft CustomCreationConverter that takes the arguments in the constructor and then using reflection in the Create method. – FalcoGer Sep 06 '19 at 13:07
1

You are trying to create a self-describing class.

There are 3 methods:

  1. Dependency injection: use a framework such as unity (heavy but may really be what you want)
  2. Custom Attributes: to add metadata to the class and then scan the metadata
  3. Self-registering classes (instantiate and destroy each subclass to access mustoverride properties). Makes use of the factory pattern.

Custom Attributes:

This is a simple example I made:

Imports System.Linq
Imports System.Runtime.CompilerServices

<AttributeUsage(System.AttributeTargets.[Class])>
Public Class SelfDescribingClassAttribute
    Inherits System.Attribute
    Public Property Name As String
    Public Sub New(Name As String)
        Me.Name = Name
    End Sub
End Class

<SelfDescribingClassAttribute("ExampleClassName")>
Public Class ExampleClass

End Class

Public Module SelfDescribingClassTools

    Public Function GetNameOfSelfDescribingClass(ClassType As Type) As String
        Try
            GetNameOfSelfDescribingClass = ClassType.GetAttributeValue(Function(SelfDescribingClass As SelfDescribingClassAttribute) SelfDescribingClass.Name)
        Catch ex As Exception
            Return String.Empty
        End Try
    End Function

    Public Function GetDictionaryOfSelfDescribingClasses(Of T)() As Dictionary(Of String, Type)
        GetDictionaryOfSelfDescribingClasses = New Dictionary(Of String, Type)
        Dim Subclasses As Type() = GetSubClasses(Of T)()
        For Each Subclass As Type In Subclasses
            Try
                Dim name As String = GetNameOfSelfDescribingClass(Subclass)
                If Not String.IsNullOrWhiteSpace(name) Then
                    GetDictionaryOfSelfDescribingClasses.Add(name, Subclass)
                End If
            Catch ex As Exception
                Debug.Print(ex.ToString)
            End Try
        Next
    End Function

    Public Function GetSubClasses(Of T)() As Type()
        Dim baseType As Type = GetType(T)
        Dim assembly As Reflection.Assembly = baseType.Assembly
        Return assembly.GetTypes().Where(Function(x) x.IsSubclassOf(baseType))
    End Function

    <Extension()>
    Function GetAttributeValue(Of TAttribute As Attribute, TValue)(ByVal type As Type, ByVal valueSelector As Func(Of TAttribute, TValue)) As TValue
        Dim att = TryCast(type.GetCustomAttributes(GetType(TAttribute), True).FirstOrDefault(), TAttribute)
        If att IsNot Nothing Then
            Return valueSelector(att)
        End If

        Return Nothing
    End Function

End Module

Self-registering classes:

This is a really good writeup with examples: http://www.jkfill.com/2010/12/29/self-registering-factories-in-c-sharp/

From the site:

DataType.cs:

using System;
using System.Collections.Generic;
using System.Reflection;

namespace SelfRegisteringFactory
{
    public abstract class DataType
    {
        public static DataType Create(string typeName)
        {
            Type derivedType = null;
            if (sTypeMap.TryGetValue(typeName, out derivedType))
            {
                return System.Activator.CreateInstance(derivedType)
                    as DataType;
            }
            return null;
        }


        public abstract string GetDefaultValue();


        protected abstract string GetTypeName();

        private static Dictionary<string, Type> sTypeMap = CreateTypeMap();

        private static Dictionary<string, Type> CreateTypeMap()
        {
            Dictionary<string, Type> typeMap =
                new Dictionary<string, Type>();

            Assembly currAssembly = Assembly.GetExecutingAssembly();

            Type baseType = typeof(DataType);

            foreach (Type type in currAssembly.GetTypes())
            {
                if (!type.IsClass || type.IsAbstract ||
                    !type.IsSubclassOf(baseType))
                {
                    continue;
                }

                DataType derivedObject =
                    System.Activator.CreateInstance(type) as DataType;
                if (derivedObject != null)
                {
                    typeMap.Add(
                        derivedObject.GetTypeName(),
                        derivedObject.GetType());
                }
            }

            return typeMap;
        }
    }
}

BooleanDataType.cs:

using System;

namespace SelfRegisteringFactory
{
    public class BooleanDataType : DataType
    {
        public BooleanDataType()
        {
        }

        public override string GetDefaultValue()
        {
            return "false";
        }

        protected override string GetTypeName()
        {
            return "bool";
        }
    }
}

IntegerDataType.cs:

using System;

namespace SelfRegisteringFactory
{
    public class IntegerDataType : DataType
    {
        public IntegerDataType ()
        {
        }

        public override string GetDefaultValue ()
        {
            return "0";
        }

        protected override string GetTypeName ()
        {
            return "int";
        }
    }
}

Main.cs:

using System;

namespace SelfRegisteringFactory
{
    class MainClass
    {
        public static void Main (string[] args)
        {
            PrintDefaultForType("bool");
            PrintDefaultForType("int");
        }

        public static void PrintDefaultForType(string typeName)
        {
            DataType dataType = DataType.Create(typeName);

            if (dataType != null)
            {
                Console.WriteLine(dataType.GetDefaultValue());
            }
            else
            {
                Console.WriteLine("unknown");
            }
        }
    }
}
VoteCoffee
  • 4,692
  • 1
  • 41
  • 44