简体   繁体   中英

C# derived class and base constructor parameter logic

I have a base class:

public class Base
{
    public Base(X x ,Y y){
        this.x = x;
        this.y = y;
    }

    public X x{get;}
    public Y y{get;}
}

and a deriver:

public class Derive : Base
{
    public Derive(Z z, Q q) :Base (? ?)
    {
    }

    private void ConstructXY(Z z, Q q)
    {
        //Advanced logic for creating an X and a Y
        if(q.something == 5){
            this.x = new X(28); 
        }
        else{
            this.x = new X(25);
        }

        if(this.x.something == 25 && q == 9){
            this.y = new Y(1);
        }
        else{
            this.y = new Y(5)
        }
    }
}

Now I can't correctly call the base constuctor without the "advanced" logic. I used to be able to call ConstructXY() from Derive.ctor() and set x and y from there, this is no longer valid since I removed the x and y setters. My real-life scenario contains a lot more logic so I am not willing to create a ternary mess.

You can call your "advanced" logic if it fits inside a static method

Here is an approach using Tuple available in C# 7 :

public class Base
{
    // This constructor was added to avoid calling twice ConstructXY
    public Base((X x, Y y) tuple) :
        this (tuple.x, tuple.y)
    {

    }

    public Base(X x, Y y)
    {
        this.x = x;
        this.y = y;
    }

    public X x { get; }
    public Y y { get; }
}

public class Derive : Base
{
    public Derive(Z z, Q q) : base(ConstructXY(z, q))
    {
    }

    private static (X x, Y y) ConstructXY(Z z, Q q)
    {
        X x;
        Y y;

        //Advanced logic for creating an X and a Y
        if (q.something == 5)
        {
            x = new X(5);
        }
        else
        {
            x = new X(25);
        }

        if (x.something == 25 && q == 9)
        {
            y = new Y(1);
        }
        else
        {
            y = new Y(5)
        }

        return (x, y);
    }
}

If you cannot change the access modifiers of X and Y in the base class so they are accessible for subclasses, then you will have to follow the contract, leaving the constructor the only possible place where you can set those members.

The only way to add more logic to calculate those values would be to use static methods, for example like this:

public class Derive : Base
{
    public Derive(Z z, Q q)
        : base(ConstructX(q), ConstructY(q, z))
    { }

    private static X ConstructX(Q q)
    {
        if (q.something == 5)
            return new X(28);
        else
            return new X(25);
    }

    private static Y ConstructY(Q q, Z z)
    {
        if (z.something == 25 && q.something == 9)
            return new Y(1);
        else
            return new Y(5);
    }
}

Since these are separate method calls, you cannot calculate both values “at once”, so you cannot make the result of Y depend on the result of X without redoing the calculation based on Z and Q again.

Another way you could solve this is by removing the public constructor on Derive altogether and provide a static factory method instead:

public class Derive : Base
{
    private Derive(X x, Y y)
        : base(x, y)
    { }

    public static Derive Create(Z z, Q q)
    {
        // here you can use your original logic to calculate X and Y
        X x = …
        Y y = …

        return new Derive(x, y);
    }
}

Depending on your complexity of those calculations, this might be the better solution. If you do need the original Z and Q values, just extend the private constructor to also take those and store them as well:

private Derive(Z z, Q q, X x, Y y)
    : base(x, y)
{
    this.Z = z;
    this.Q = q;
}

Why not declare your base class setters as private:

public class Base
{
    public Base(X x ,Y y){
        this.x = x;
        this.y = y;
    }

    public X x{get; private set;}
    public Y y{get; private set;}
}

That way you can still set them in the constructor, and they would not be settable outside?

I don't really like this way of doing it and would avoid it if possible but you can use a ternary statement in your call to base :

public Derive(Z z, Q q)
    : base(new X(xCondition ? 28 : 25), new Y(yCondition ? 1 : 5))

Use static methods to do the conversion. Like this.

public class Derive : Base
{
    public Derive(Z z, Q q) :base (ConvertToX(z, q), ConvertToY(z, q))
    {
    }

    private static X ConvertToX(Z z, Q q) {
        if(q.something == 5){
            return new X(28); 
        }
        return new X(25);
    }

    private static Y ConvertToY(Z z, Q q) {
        // TODO
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM