0

So I am designing a framework that has multiple classes, which derive from one another in a cascading fashion in order to perform more and more specific tasks. Each class also has its own settings, which are their own classes. However, the settings should have a parallel inheritance relationship to that of the classes themselves. It is important to the nature of this question that the settings MUST be separate from the class itself as they have constraints specific to my framework. (Edit: I also want to avoid the Composition pattern with settings as it greatly complicates the workflow in the framework I am using. So the settings must be single objects, not compositions of multiple objects.) That is I think of this as sort of like two parallel class hierarchies. One relevant example would perhaps be this:

Suppose you have a Vehicle class, and an accompanying class, VehicleSettings, that stores the relevant settings every vehicle should have, say topSpeed and acceleration.

Then suppose you have now a Car and an Airplane class, both of which inherit from the Vehicle class. But now you also create CarSettings, which inherits from the VehicleSettings class but adds the member gear. Then also have AirplaneSettings, inheriting from VehicleSettings, but adding the member drag.

To show how I might continue this pattern, suppose now I have a new class SportsCar, which inherits from Car. I create corresponding settings SportsCarSettings, which inherits from CarSettings and I add a member sportMode.

What is the best way to set up such a class hierarchy such that I can also account for the derived classes for the settings?

Ideally, I would like such an example to work like this:

public class VehicleSettings
{
    public float topSpeed;
    public float acceleration;
    // I am explicitly not adding a constructor as these settings get initialized and
    // modified by another object
}

public class Vehicle
{
    public float speed = 0;
    protected VehicleSettings settings;
    
    // Similarly here, there is no constructor since it is necessary for my purposes 
    // to have another object assign and change the settings
    
    protected virtual float SpeedEquation(float dt)
    {
        return settings.acceleration * dt;
    }
    
    public virtual void UpdateSpeed(float dt)
    {
        speed += SpeedEquation(dt)
        if(speed > settings.topSpeed)
        {
            speed = settings.topSpeed;
        }
    }
}

public class CarSettings : VehicleSettings
{
    public int gear;
}
    
public class Car : Vehicle
{
    // Won't compile
    public CarSettings settings;
    
    // Example of the derived class needing to use
    // its own settings in an override of a parent
    // method
    protected override float SpeedEquation(float dt)
    {
        return base.SpeedEquation(dt) * settings.gear;
    }
}

public class AirplaneSettings : VehicleSettings
{
    public float drag;
}
    
public class Airplane : Vehicle
{
    // Won't compile
    public Airplane settings;
    
    // Another example of the derived class needing to use
    // its own settings in an override of a parent
    // method
    public override void UpdateSpeed(float dt)
    {
        base.UpdateSpeed(dt) 
        speed -= settings.drag * dt;
    }
}

public class SportsCarSettings : CarSettings
{
    public bool sportMode;
}
    
public class SportsCar : Car
{
    // Won't compile
    public SportsCarSettings settings;
    
    // Here is again an example of a further derived class needing
    // to use its own settings to override a parent method
    // This example is here to negate the notion of using generic
    // types only once and not in the more complicated way mentioned
    // below
    public override float SpeedEquation(float dt)
    {
        return (settings.acceleration + (settings.sportMode ? 2 : 1)) * dt;
    }
}

Looking at a few possible solutions

  1. Use generic types. For example, I could have it so that it is public class Vehicle<T> : where T : VehicleSettings and I could have it have the line T settings so that when Car and Airplane inherit from Vehicle they can do that like this: public Car<T> : Vehicle<T> where T : CarSettings and public Airplane<T> : Vehicle<T> where T : AirplaneSettings, and so on. This does complicate things somewhat if I want to instantiate, for example, a Car without plugging in a generic type. Because then I would have to create a child class Car of Car<T> as follows: public class Car : Car<CarSettings> { }. And I would have to do similarly for every derived type.

  2. Use type casting in the necessary methods. For example, I could modify the Car class as follows:

     public class Car : Vehicle
     {
         // Don't reassign settings and instead leave them
         // as VehicleSettings
    
         // Cast settings to CarSettings and store that copy
         // locally for use in the method
         protected override float SpeedEquation(float dt)
         {
             CarSettings settings = (CarSettings)this.settings;
             return base.SpeedEquation(dt) * settings.gear;
         }
     }
    
  3. I also saw one recommendation to use properties as in this example, but this seems very clunky mainly since it doesn't seem to actually solve the problem. You would still have to cast the returned value to your desired type even if the dynamic type is returned from the property. If that is the proper way to go about it, I would appreciate an explanation as to how to properly implement that.

  4. It may be possible to use the new keyword to hide the parent class's version of settings and replace it with a new variable called settings of the correspond child class's settings type, but I believe this is generally advised against, mainly for reasons of complicating the relationship to the original parent class's 'settings', which affects the scope of that member in inherited methods.

So my question is which is the best solution to this problem? Is it one of these approaches, or am I missing something pretty significant in the C# syntax?

Edit:

Since there has been some mention of the Curiously Recurring Template Pattern or CRTP, I would like to mention how I think this is different.

In the CRTP, you have something like Class<T> : where T : Class<T>. Or similarly you might confront something like, Derived<T> : T where T : Base<Derived>.

However, that is more about a class which needs to interact with an objects that are of the same type as itself or a derived class that needs to interact with base class objects that need to interact with derived class objects. Either way, the relationship there is circular.

Here, the relationship is parallel. It's not that a Car will ever interact with Car, or that a SportsCar will interact with a Vehicle. Its that a Car needs to have Car settings. A SportsCar needs to have SportsCar settings, but those settings only change slightly as you move up the inheritance tree. So I think it seems kind of nonsensical that if such a deeply OO language like C# requires jumping through so many hoops to support "parallel inheritance", or in other words that it isn't just the object itself which "evolve" in their relationship from the parent to the child, but also that the members themselves do so.

When you don't have a strongly typed language, say Python for example, you get this concept for free since for our example, so long as the settings that I assigned to any particular instance of an object had the relevant properties, its type would be irrelevant. So I suppose it's more that sometimes the strongly typed paradigm can be a hindrance in that I want an object to be defined by its accessible properties rather than its type in this case with settings. And the strongly typed system in C# forces me to make templates of templates or some other strange construct to get around that.

Edit 2:

I have found a substantial issue with option 1. Suppose I wanted to make a list of vehicles and a list of settings. Suppose I have a Vehicle, a Car, an Airplane and a SportsCar, and I want to put them into a list and then iterate over the list of vehicles and settings and assign each setting to its respective vehicle. The settings can be put in a list of type VehicleSettings, however there is no type (other than Object) that the vehicles can be put in since the parent class of Vehicle is Vehicle<VehicleSettings>, the parent class of Car is Car<CarSettings>, etc. Therefore what you lose with generics is the clean parent child hierarchy that makes grouping similar objects into lists, dictionaries, and the like so comfortable.

However, with option 2, this is possible. If there is no way to avoid the aforementioned problem, option 2, despite being uncomfortable in some respects, seems the most manageable way to do it.

Orren Ravid
  • 560
  • 1
  • 6
  • 24
  • Why should the settings classes inherit from each other? I think `SportsCar` should have 3 settings: `vehicleSettings`, `carSettings` and `sportsCarSettings`. – Sweeper Jan 10 '21 at 02:10
  • Does this answer your question? [How to write a good curiously recurring template pattern (CRTP) in C#](https://stackoverflow.com/questions/10939907/how-to-write-a-good-curiously-recurring-template-pattern-crtp-in-c-sharp) – Charlieface Jan 10 '21 at 02:11
  • Also the great @EricLippert talks about it [Curiouser and curiouser](https://ericlippert.com/2011/02/02/curiouser-and-curiouser/) – Charlieface Jan 10 '21 at 02:16
  • @Sweeper, I explicitly require them to be singular settings for my purposes. These settings in my case are actually serialized objects. In particular, I am working with Unity and I am setting up a system so that each object can have its own settings that I can tweak in the inspector. The way the UI is structured, having more than one settings object per object can become very clumsy to work with. – Orren Ravid Jan 10 '21 at 02:16
  • @Charlieface, correct me if I'm wrong but my issue is ensuring that the type of the settings is constrained in parallel to the corresponding object. It seems that the CRTP is more about a derived type using the base class as a generic. I am having trouble seeing the relationship. – Orren Ravid Jan 10 '21 at 02:25
  • I don't know too much about Unity, but can the UI still work if `settings` were a property? – Sweeper Jan 10 '21 at 02:27
  • @Sweeper, I think it depends on how the property is implemented. If the property performs some operation on a regular member and gets and sets that, then you can still access the original member, if that helps. There are also some additional tools that have been developed I think to allow for directly accessing the property in the UI. So for the purposes of this question, I would accept an implementation using a property if it was the optimal solution to the problem. – Orren Ravid Jan 10 '21 at 02:31
  • I think something similar to CRTP is applicable, except not quite so curious. You can make `Vehicle where TSettings : VehicleSettings`, use `TSettings` as the type for the field. This constrains Car to be defined `Car : Vehicle` and immediately you have an instance of CarSettings in that field. *Edit* Or basically option 1 – Charlieface Jan 10 '21 at 03:02
  • This looks too messy and over(or under)engineered but... You can make generics in `Vehicle` and make the method which assings settings be aware of the actual classtype to put the right settings class. Option 1 is the way IMHO. A good dictionary of settings should do the same work with (much) less effort. Strong compllicated hierarchical OOP is not always the best option – DrkDeveloper Jan 10 '21 at 03:22
  • @DrkDeveloper Well here's my issue with that: code maintenance. Suppose I have like 10 different types of objects, or 20, or 30. Each needs its own settings. This means that every time I make a new sort of vehicle, I need to go back into my method that assigns the settings and make sure I have added the new types so that it can be aware of them. Now imagine you share that with other people and they make their own vehicles, who will be ensuring that the main class that assigns settings is mindful of that? And my project will require that sort of scope. So I'm not sure this is overengineered – Orren Ravid Jan 10 '21 at 03:38
  • "When you don't have a strongly typed language, say Python for example, you get this concept for free since for our example, so long as the settings that I assigned to any particular instance of an object had the relevant properties, its type would be irrelevant.". And I think, "oh, a dictionary of settings". Class Types are fancy modern types of "dictionary of named values". I repeat, I think OOP is not the way. – DrkDeveloper Jan 10 '21 at 03:40
  • @Charlieface, I have found another problem with option 1, suppose I want to collect all my vehicles into a list and all my settings into another and assign each setting to its corresponding vehicle, what type should those lists be? The settings can be of type VehicleSettings, but the templated types actually stop me from putting a Car, a Plane, and lets say a SportsCar all in a list of Vehicles. The only shared class they all have at that point is Object – Orren Ravid Jan 10 '21 at 03:42
  • @DrkDeveloper, unfortunately I am constrained to using OOP here for one central reason. The main one is that my settings aren't just any old object but a particular type of serializable object that makes them editable in the Unity Game Engine UI. Therefore they can't be dictionaries. But I understand your argument, I may just not be able to have my cake and eat it too. – Orren Ravid Jan 10 '21 at 03:48
  • I'm not in Unity programming but let me catch all of this. You're doing a game, and you're doing classes of vehicles, but you want to let modders to add vehicles to your game. Isn't it? I searched a little about dictionaries in unity (right now). Can you use this? https://forum.unity.com/threads/finally-a-serializable-dictionary-for-unity-extracted-from-system-collections-generic.335797/ – DrkDeveloper Jan 10 '21 at 03:57
  • That whole conception is a bit odd. You wouldn't store them separately at all, they would be nested. How would you know which one goes where anyway? I suppose you can just use `as Car` to check if it's the right type. – Charlieface Jan 10 '21 at 03:58
  • @OrrenRavid look at this too https://www.youtube.com/watch?v=OGnhLL4q_F8 – DrkDeveloper Jan 10 '21 at 03:59
  • @DrkDeveloper well I'm not doing vehicles. That was an example, but the same paradigm applies to the problem I am trying to solve. I couldn't post the actual source code for reasons of privacy, so I made this toy example to try to demonstrate a similar idea. But yes I want modders/other developers to be able to add their own "vehicles" without worrying about affecting any other parts of the codebase. And a downside to using dictionaries, is once again, the type constraint. The information I store for each settings could be of many types. – Orren Ravid Jan 10 '21 at 04:03
  • @Charlieface this strange way of doing things sort of comes from how Unity handles assigning components to game objects, so I unfortunately have to live with that constraint. But the problem again with something like `as Car` is that for each new type of vehicle, I would have to have another type check, which as I explained to DrkDeveloper, in a previous comment is simply untenable if I want to allow this codebase to grow manageably and be modified by other developers – Orren Ravid Jan 10 '21 at 04:06
  • @OrrenRavid Ok, yeah, then you should check that links, and don't overfocus in one idea, because I think this is a clear case of "when you have a hammer, everything looks like a nail". I'm pretty sure dictionaries are the way to do it, are simplier in all ways for you, and for future modders (look at minecraft tags, it's all dictionaries). It's a little effort to rewrite, but it is worth it. Sorry I can't help you more, I'm not in Unity things – DrkDeveloper Jan 10 '21 at 04:07
  • @DrkDeveloper Thanks for the suggestion, however I'm not sure I can so quickly give up the convenience of Unity's SerializedObject due to its utility for playtesting and iteration https://docs.unity3d.com/ScriptReference/SerializedObject.html. So unfortunately, I might have to stick to that over dictionaries. Thanks for your help – Orren Ravid Jan 10 '21 at 04:11
  • Being that you have to loop through each `Settings` and assign it, you could write a function on `Vehicle` as follows `bool AssignSettings(Settings settings) { if(settings is TSettings settings1) {_settings = settings1; return true;} else return false; }` and you could pass the settings object to each until you get a `true` – Charlieface Jan 10 '21 at 04:13
  • @Charlieface, that's true, but that's not the bigger problem for the looping construct, the first question is how can I even collect all the Vehicles into a shared list in the first place. As I point out in my second edit, they have no shared type when using generics, other than Object. And if I put them in a list of type Object, then I wouldn't be able to call AssignSettings without a cast. And a cast would require me to iterate over all possible types, which will constantly be changing. – Orren Ravid Jan 10 '21 at 04:26
  • Make a non-generic base class for the Vehicle – Charlieface Jan 10 '21 at 04:29
  • Right, but then that sort of brings us back to option 2, right? Since the whole point of generic types was to avoid explicitly assigning the type to settings in Vehicle. If Vehicle base class is not generic then the settings will be explicitly assigned, bringing us back to the original problem. If you are suggesting to make a base class lower in the hierarchy than Vehicle, without settings, then I can't make an AssignSettings function. It's a catch-22 in that sense. – Orren Ravid Jan 10 '21 at 04:40

1 Answers1

2

For my proposed solutions, we will use cast, which makes it similar to solution 2. But we will only have them in properties (but not properties like solution 3). We will also use the new keyword as in solution 4.

I avoided solutions using generics (solution 1) because of the issue described under "Edit 2" on the question. However, I want to mention that you could have class Vehicle<T> : Vehicle where T : VehicleSettings, which I believe is what Charlieface meant in comments. As per that bringing back to solution 2, well, you can do it like I do in this answer plus generics if you want.


Proposed solution

This is my proposed solution:

class VehicleSettings{}

class Vehicle
{
    private VehicleSettings _settings;

    public VehicleSettings Settings{get => _settings; set => SetSettings(value);}
    
    protected virtual void SetSettings(VehicleSettings settings)
    {
        _settings = settings;
    }
}

Notice, I am creating a private field. This is important. It allows us to control access to it. We will control how we read it, and how we write it.

We will only read it in the Settings getter:

public VehicleSettings Settings{get => _settings; set => SetSettings(value);}

If you imagine that _settings may contain a derived type, there is no problem in going from that to the base type. Thus, we don't need derived classes to mess with that part.

On the other hand, we will only write it the SetSettings method:

    protected virtual void SetSettings(VehicleSettings settings)
    {
        _settings = settings;
    }

This method is virtual. This will allow us to throw if other code is trying to set the wrong settings. For example, if we have a class derived from Vehicle, say Car, but it is stored as a variable of type Vehicle and we try to write a VehicleSettings to Settings... The compiler can't prevent this. As far as the compiler knows, the type is correct. The best we can do is throw.

Remember that those must be the only places where _settings is used. So, any other method must use the property:

class VehicleSettings
{
    public float Acceleration;
}

class Vehicle
{
    private VehicleSettings _settings;

    public VehicleSettings Settings{get => _settings; set => OnSetSettings(value);}
    
    protected virtual void OnSetSettings(VehicleSettings settings)
    {
        _settings = settings;
    }
    
    public virtual float SpeedEquation(float dt)
    {
        return Settings.Acceleration * dt;
    }
}

And that would, evidently, extend to derived classes. This is how you go about implementing them:

class CarSettings : VehicleSettings
{
    public int Gear;
}

class Car : Vehicle
{   
    public new CarSettings Settings
    {
        get => (CarSettings)base.Settings; // should not throw
        set => base.Settings = value;
    }
    
    protected override void OnSetSettings(VehicleSettings settings)
    {
        Settings = (CarSettings)settings; // can throw
    }
    
    public override float SpeedEquation(float dt)
    {
        return base.SpeedEquation(dt) * Settings.Gear;
    }
}

Now, I have added a new Settings property, that has the correct type. That way, code using it will get it. However, we do not want to add a new backing field. So this property will simply delegate to the base property. And that should be the only place where we access the base property.

We also know that the base class will call OnSetSettings, so we need to override that. It is important that OnSetSettings ensures the type is correct. And by using the new property instead of the property in the base type, the compiler reminds us to cast.

Note that these casts will result in an exception if the type is wrong. However, the getter should not throw as long as OnSetSettings is correct and you don't write to _settings anywhere else. You may even consider to write the getter with base.Settings as CarSettings.

Furthermore, you only need those two casts. The rest of the code can, and should, use the property.

Third generation class? Sure. Same pattern:

class SportsCarSettings : CarSettings
{
    public bool SportMode;
}

class SportsCar : Car
{
    public new SportsCarSettings Settings
    {
        get => (SportsCarSettings)base.Settings;
        set => base.Settings = value;
    }
    
    protected override void OnSetSettings(VehicleSettings settings)
    {
        Settings = (SportsCarSettings)settings;
    }
    
    public override float SpeedEquation(float dt)
    {
        return (Settings.Acceleration + (Settings.SportMode ? 2 : 1)) * dt;
    }
}

As you can see, one drawback of this solution is that it needs some level of extra discipline. The developer must remember to not access _settings other than the getter of Settings and the method SetSetting. Similarly do not access the base Settings property outside of the Settings property (in the derived class).

Consider using code generation to implement the pattern. Either T4, or perhaps the newly fangled Source Generators.


If we don't need setters

Do you need to write the settings? As I explained earlier, the setter may throw. If you have a variable of type Vehicle that has an object of type Car and try to set VehiculeSettings to it (or something else, like SportCarSettings). However, the getter does not throw.

In C# 9.0 there is return type covariance for virtual methods (and properties, actually). So you can do this:

class VehicleSettings {}

class CarSettings: VehicleSettings {}

class Vehicle
{
    public virtual VehicleSettings Settings
    {
        get;
        //set; // won't compile
    }
}

class Car: Vehicle
{
    public override CarSettings Settings
    {
        get;
        //set; // won't compile
    }
}

But if you add the setters, that won't compile. Ah, but you could easily add a "SetSettings" methods. It would make usage somewhat backwards, but implementation easier:

class VehicleSettings {}

class Vehicle
{
    private VehicleSettings _settings;
    
    public virtual VehicleSettings Settings => _settings;
    
    public void SetSettings(VehicleSettings Settings)
    {
        OnSetSettings(Settings);
    }
    
    protected virtual void OnSetSettings(VehicleSettings settings)
    {
        _settings = settings;
    }
}

Similarly to the proposed solution, we will restrict access to the backing field. The difference is that now the property is virtual and does not have a setter (so we can override it with the appropiate type, instead of using the new keyword), instead we have a SetSettings method.

class CarSettings: VehicleSettings {}

class Car: Vehicle
{
    public override CarSettings Settings{get;}
    
    public void SetSettings(CarSettings settings)
    {
        base.OnSetSettings(settings);
    }

    protected override void OnSetSettings(VehicleSettings settings)
    {
        SetSettings((CarSettings)settings);
    }
}

Also notice we don't have to use the keyword new on the methods. Instead, we are adding a new overload. Just like the proposed solution, this one requires dicipline. It has the drawback that not casting is not a compile error, it is just calling the overload (which results in a stack overflow, and I'm not talking about this website).


If we can have constructors

Are constructors a no-no? Because you could do this:

class VehicleSettings {}

class Vehicle
{
    protected VehicleSettings SettingsField = new VehicleSettings();

    public VehicleSettings Settings
    {
        get => SettingsField;
        set
        {
            if (SettingsField.GetType() == value.GetType())
            {
                SettingsField = value;
                return;
            }

            throw new ArgumentException();            
        }
    }
}

And derived class:

class CarSettings: VehicleSettings {}

class Car: Vehicle
{
    public Car()
    {
        SettingsField = new CarSettings();
    }

    public new CarSettings Settings
    {
        get => (CarSettings)base.Settings;
        set => base.Settings = value;
    }
}

Here, the setter enforces that the new value of Settings must be of the same type it currently has. That of course, poses a problem: we need to initialize it with the valid type, thus we need a constructor. I still add a new property for convinience.

One important advantage is that nothing is virtual. So we don't need to worry about overriding and overriding correctly. We still need to remember to initialize.


I considered using IsAssignableFrom or IsInstanceOfType, but that would allow to narrow the type. That is, it would allow to assign a SportsCarSettings to a Car, but then you would not be able to assign a CarSettings back.

A workaround would be store the type… Set it from the constructor (or a custom attribute). So you would declare protected Type SettingsType = typeof(VehicleSettings); set it from the derived class constructor with SettingsType = typeof(CarSettings); and compare using SettingsType.IsAssignableFrom(value?.GetType()).

Now, if you ask me, that is a lot of repetition.


If we can have reflection

Look at this code:

class CarSettings: VehicleSettings {}

class Car: Vehicle
{
    public new CarSettings Settings
    {
        get => (CarSettings)base.Settings;
        set => base.Settings = value;
    }
}

Can it work? With reflection! By declaring this property we have specified the type that the base class should test against.

The base class is this creature:

class VehicleSettings {}

class Vehicle
{
    protected VehicleSettings? SettingsField;
    private Type? SettingsType;

    public VehicleSettings Settings
    {
        get => SettingsField ??= new VehicleSettings();
        set
        {
            SettingsType ??= this.GetType().GetProperties()
                .First(p => p.DeclaringType == this.GetType() && p.Name == nameof(Settings))
                .PropertyType;
            if (SettingsType.IsAssignableFrom(value?.GetType()))
            {
                SettingsField = value;
                return;
            }

            throw new ArgumentException();            
        }
    }
}

This time I have opted for lazy initialization, so no constructor. However, yes, that reflection works in constructor. It is getting the type of the property declared on the derived type via reflection, and checking if the value that we are trying to set matches.

I had to use GetProperties because GetProperty would either give me System.Reflection.AmbiguousMatchException or null, depending on binding flags.

A consequence is that forgetting to declare Settings in the derived class means that you won't be able to set the property (it will throw because it does find the property).

And, yes, this continues for future generations. Just don't forget to declare a new Settings:

class SportsCarSettings : CarSettings{}

class SportsCar: Car
{
    public new SportsCarSettings Settings
    {
        get => (SportsCarSettings)base.Settings;
        set => base.Settings = value;
    }
}
Theraot
  • 31,890
  • 5
  • 57
  • 86
  • Thanks for the super in depth answer. Just saw this recently. I will have to look at it in a bit more detail and try out some of the suggestions to see if they improve over my current solution. – Orren Ravid May 04 '21 at 04:32
  • Once I try these out, if one of these ends up solving my issue, I will gladly mark this as the solution. – Orren Ravid May 04 '21 at 04:33
  • I would say that since I’m working in a very specific framework, these solutions don’t exactly work for my exact use case. However, I failed to specify that I am using the Unity game engine which has some interesting quirks when it comes to getters, setters, and the like. So I will accept your answer as I think as general solutions to the problem I mentioned, you have provided some good comprehensive ones. – Orren Ravid Jun 23 '21 at 11:23