Search code examples
c#inheritanceinterfacepolymorphismcode-injection

C# - Extend/Add interface to parameter of overridden method


I am pretty new to C#. I am trying to derive a class UIObjectContainer : UIObject which overrides a method FillProperties which 'fills' in a parameter Properties (just holds misc data).

I want to extend this parameter in the derived class to 'de-clutter' the base class and allow Properties instances to only contain necessary properties instead of all possible ones.

In my current usage, I simply stash several variables inside Properties and then access them in the derived class. Each null property still consumes 4-8 bytes depending on the system, compared to a Dictionary of properties which requires 20 bytes per property just having null properties seems preferable for my scenario.

This is a simplification of my current code:

public class Properties 
{
    public Color basePropertyColor;
    public List<Color> containerPropertyItemColors;
}

public class UIObject 
{
    public virtual void FillProperties(Properties properties) 
    {
        properties.basePropertyColor = Color.grey;
    }
}

public class UIObjectContainer : UIObject 
{
    public override void FillProperties(Properties properties) 
    {
        properties.containerPropertyItemColors = new List<Color>() { Color.red };
        base.FillProperties(properties);
    }
}

The drawback of this code is that Properties consumes unnecessary memory and contains several parameters which are grouped together in what seems like should be an interface on top of Properties.

Ideally this interface (or derived class) would be injected into the properties at runtime depending on what class we are in and which requirements it has. The interface design pattern here seems promising since it allows you to later check if Properties is of a certain type and do misc logic on the properties using the is keyword.

I tried the following but it seems like while this does not cause compilation errors it does not work as intended.

public class Properties 
{
    public Color basePropertyColor;
}

public class UIObject 
{
    public virtual void FillProperties(Properties properties) 
    {
        properties.basePropertyColor = Color.grey;
    }
}

public class PropertiesContainer : Properties, PropertiesWithMultipleItems 
{
    public float containerPropertyItemColors { get { return new List<Color>() { Color.red } }; }
}

public interface PropertiesWithMultipleItems 
{
    public List<Color> containerPropertyItemColors { get; }
}

public class UIObjectContainer : UIObject 
{
    public override void FillProperties(Properties properties) 
    {
        properties = new PropertiesContainer();
        properties.containerPropertyItemColors = 1;
        base.FillProperties(properties);
    }
}

Could someone take a look at the desired implementation and let me know what design pattern Properties needs to adapt in order to be extensible at runtime in the desired way?

Extension methods: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods, are promising but seem to suffer from the same interface/grouping problem.


Solution

  • This feels like a place where generics could be useful; since you could specify the property type as a generic parameter:

    public class Properties {
        public Color basePropertyColor {get; set;}
    }
    public class UIObject<TProperties> where TProperties : Properties {
        public virtual void FillProperties(TProperties properties) {
            properties.basePropertyColor = Color.grey;
        }
    }
    
    public class PropertiesContainer : Properties {
        public float containerPropertyItemColors {get; set;}
    }
    public class UIObjectContainer : UIObject<PropertiesContainer> {
        public override void FillProperties(PropertiesContainer properties) {
            properties = new PropertiesContainer();
            properties.containerPropertyItemColors = 1;
            base.FillProperties(properties);
        }
    }
    

    Personally I would change FillProperties to return the PropertiesContainer instead of mutating your class state just to keep things cleaner as well. That would allow you to do UIObject as a template pattern to enforce that the derived FillProperties method exists and is called as well.