Home > database >  C# Using class specific member references that child "base" calls respect
C# Using class specific member references that child "base" calls respect

Time:04-01

I'm currently working on a codebase and struggling to find an optimal and clean solution. I've removed the context of the problem to help simplify it to its root components. The Scale property is a simplification for a more complex state of the class in the actual codebase. I have an idea (which I'll reference at the bottom) for how I could solve this issue - however the solution feels messy and just avoids the area I want to better understand.

Class Hierarchy

public class GreatGrandparent
{
    public virtual int Scale { get; set; } = 1;
    public virtual int GetTrueScale()
    {
        return Scale;
    }
}

public class Grandparent : GreatGrandparent
{
    public override int Scale { get; set; } = 2;
    public override int GetTrueScale()
    {
        return Scale * base.GetTrueScale();
    }
}

public class Parent : Grandparent
{
    public override int Scale { get; set; } = 8;
}

public class Child : Parent
{
    public override int Scale { get; set; } = 4;
}

Somewhere else in code:

public class Main 
{
    Child aChild = new Child();
    int aChildTrueScale = aChild.GetTrueScale();
}
  • Expected Result: 4 (4×1) (Refer to Edit 1)
  • Actual Result: 16 (4×4)
  • Desired Result: 64 (4×8×2×1)

I want a child to find its relative scale by taking in all factors of scale from its parents, so that would like:

child relative scale = child scale × parent scale × … × base class scale

How can I (if possible) define the GetTrueScale method once in the parent class to get the desired result - which all children inherit - to avoid continuously overriding the method with duplicate implementations (the exception being the GreatGrandparent).

"Messy" Solution

Define a separate property/field in each class, and continuously override the aChildTrueScale() method with a return of ClassScale * base.GetTrueScale() where the ClassScale is a different property on each Class.

Edit 1

The expected result was my initial expectation based on my understanding at the time - thinking that within a base call the Scale reference would respect the change in scope change value to match that of the base class. With some further testing it appears that regardless of what scope when a base method is called, the referenced Scale value is always from the initial objects scope (hence 4*4).

Is it possible to refer to properties based on their scope? So in a base.GetTrueScale() call, any references within that function call will be on the base scope. Or am I completely missing something/trying to over simplify children?

Footnote

I've got a a bit of experience with procedural programming around data science, however I'm fairly inexperienced with object-oriented programming so forgive me if I'm ignorant to some core concepts. I’m happy to help clarify anything, thanks for taking the time to look over my first question! ^-^

(If anyone can think of a better title please let me know and I'll fix it up - was struggling to define the issue simply)

CodePudding user response:

I'd suggest using the type system to model this hierarchy is a mistake. You're wanting Child, Parent, GrandParent to be separate, independent things. That doesn't suggest the is-a relationship that you typically expect in the type system.

So instead have:

public class Thingy {
    public int Scale {get;set;}
    public Thingy Parent {get;set;}

    public int GetTrueScale()
    {
        var current = this;
        var accumulator = current.Scale;
        current = current.Parent;
        while(current!=null)
        {
            accumulator = accumulator * current.Scale;
            current = current.Parent;
        }
        return accumulator;
    }
}

And then create each of your objects:

var greatGrandParent = new Thingy {Scale = 1};
var grandParent = new Thingy {Scale = 2, Parent = greatGrandParent};
var parent = new Thingy {Scale = 8, Parent = grandParent};
var child = new Thingy { Scale = 4, Parent = parent};

And you can now call child.GetTrueScale() and all levels of the hierarchy are taken into consideration.

Adding Children sets to Thingy and other more interesting behaviours is left as a exercise.


Thingy itself could also be an IThingy interface, if the different levels truly do require separate types.

CodePudding user response:

From the discussion in the comments, it turns out that you don't need your Scale property to be settable (its value is fixed on construction), and you don't even need it to be virtual. You can just have a single property on the GreatGrandfather class, like so:

public class GreatGrandparent
{
    public int Scale { get; }
    public GreatGrandparent(int scale)
    {
         Scale = scale;   
    }
}

public class Grandparent : GreatGrandparent
{
    public Grandparent(int scale) : base(scale * 2) { }
}

public class Parent : Grandparent
{
    public Parent(int scale) : base(scale * 8) { }
}

public class Child : Parent
{
    public Child(int scale) : base(scale * 4) { }
}

In your actual code, you're dealing with a HashSet. You could write something like this, where each child adds new items to the GreatGrandparent's HashSet:

public class GreatGrandparent
{
    protected HashSet<string> hashSet = new();
    public GreatGrandparent()
    {
         hashSet.Add("itemOne"); 
    }
}

public class Grandparent : GreatGrandparent
{
    public Grandparent()
    {
        hashSet.Add("itemTwo");
    }
}

Or pass down the items to add in the constructor chain (more expensive, but maybe neater?):

public class GreatGrandparent
{
    protected HashSet<string> hashSet;
    public GreatGrandparent(IEnumerable<string> items)
    {
        hashSet = new(items);
        hashSet.Add("itemOne");
    }
}

public class Grandparent : GreatGrandparent
{
    public Grandparent(IEnumerable<string> items) : base(items.Concat(new[] { "itemTwo" })) { }
}

CodePudding user response:

The type hierarchy will be called in the order from most base type -> most derived.

As you do not have overriden methods in Parent then your Scale is not multiplied. That it is a reason why you got 16. It is better to debug and see order of execution of your code.

You can add override GetTrueScale() method of class Parent to have desired value 64. The whole code will look like this:

public class GreatGrandparent
{
    public virtual int Scale { get; set; } = 1;

    public virtual int GetTrueScale()
    {
        Console.WriteLine("GreatGrandparent: "   Scale);
        return Scale;
    }
}

public class Grandparent : GreatGrandparent
{
    public override int Scale { get; set; } = 2;

    public override int GetTrueScale()
    {
        Console.WriteLine("Grandparent: "   Scale);
        return Scale * base.GetTrueScale();
    }
}

public class Parent : Grandparent
{
    public override int Scale { get; set; } = 8;

    public override int GetTrueScale()
    {
        Console.WriteLine("Grandparent: "   Scale);
        return Scale * base.GetTrueScale();
    }
}

and Child class:

public class Child : Parent
{
    public override int Scale { get; set; } = 4;
}
  • Related