简体   繁体   中英

How to validate classes in a hierarchy in a generic type-safe way?

I'm stuck with what seemed to be a very simple task at the very beginning. I have a class hierarchy each class in which can define its own validation rules. Defining validation rules should be as simple as possible. Here is what is almost what is needed:

class HierarchyBase
{

    private List<Func<object, bool>> rules = new List<Func<object, bool>>();
    public int fieldA = 0;

    public HierarchyBase()
    {
        AddRule(x => ((HierarchyBase)x).fieldA % 2 == 0);
    }

    protected virtual void Operation()
    {
        fieldA++;
    }

    protected void AddRule(Func<object, bool> validCriterion)
    {
        rules.Add(validCriterion);
    }

    public void PerformOperation()
    {
        Operation();
        Validate();
    }

    protected virtual void Operation()
    {
        fieldA++;
    }

    private void Validate()
    {
        IsValid = rules.All(x => x(this));
    }

    public bool IsValid
    {
        get;
        private set;
    }
}

There is one more thing that is needed - type safety when adding validation rules. Otherwise each sub class will have to do those casts that just look awkward. Ideally Func<T, bool> would work, but there is a whole bunch of issues with that: we cannot inherit our HierarchyBase from any kind of IValidatable<HierarchyBase> as the inheritance hierarchy can be N levels deep (yeah, I feel the smell as well); storing any concrete Func<HierarchyBaseInheritor, bool> in rules and traversing them.

How would you introduce type-safety here?

The right approach is to make each class in the hierarchy responsible for validating itself:

HierarchyBase:

class HierarchyBase
{
    public int A { get; set; }

    public bool Validate()
    {
        return this.OnValidate();
    }

    protected virtual bool OnValidate()
    {
        return (this.A % 2 == 0);
    }
}

HierarchyBaseInheritorA:

class HierarchyBaseInheritorA : HierarchyBase
{
    public int B { get; set; }

    protected override bool OnValidate()
    {
        return base.OnValidate() &&
               (this.A > 10) &&
               (this.B != 0);
    }
}

HierarchyBaseInheritorB:

class HierarchyBaseInheritorB : HierarchyBaseInheritorA
{
    public int C { get; set; }

    protected override bool OnValidate()
    {
        return base.OnValidate() && 
               (this.A < 20) &&
               (this.B > 0) &&
               (this.C == 0);
    }
}

Usage:

var result = new HierarchyBaseInheritorB();
result.A = 12;
result.B = 42;
result.C = 0;
bool valid = result.Validate(); // == true

Note: The following solution is an in-joke between me and Eric Lippert. It works, but is probably not to be recommended.

The idea is to define a generic type parameter that refers to the "current" type (like this refers to the "current" object).

HierarchyBase:

class HierarchyBase<T>
    where T : HierarchyBase<T>
{
    protected readonly List<Func<T, bool>> validators;

    public HierarchyBase()
    {
        validators = new List<Func<T, bool>>();
        validators.Add(x => x.A % 2 == 0);
    }

    public int A { get; set; }

    public bool Validate()
    {
        return validators.All(validator => validator((T)this));
    }
}

HierarchyBaseInheritorA:

class HierarchyBaseInheritorA<T> : HierarchyBase<T>
    where T : HierarchyBaseInheritorA<T>
{
    public HierarchyBaseInheritorA()
    {
        validators.Add(x => x.A > 10);
        validators.Add(x => x.B != 0);
    }

    public int B { get; set; }
}

HierarchyBaseInheritorB:

class HierarchyBaseInheritorB : HierarchyBaseInheritorA<HierarchyBaseInheritorB>
{
    public HierarchyBaseInheritorB()
    {
        validators.Add(x => x.A < 20);
        validators.Add(x => x.B > 0);
        validators.Add(x => x.C == 0);
    }

    public int C { get; set; }
}

Usage:

var result = new HierarchyBaseInheritorB();
result.A = 12;
result.B = 42;
result.C = 0;
bool valid = result.Validate(); // == true

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