Generic classes with inheritance/equals/comparison operators

Posted on

Problem

I would like to greatly reduce verbosity of following code fragment.

What I’d like to do:

  1. Reduce number of occurrences of MaterialRangedParam<Value> and MaterialParam<Value>. C++ has typedef. C# doesn’t. Is there keyword for own type of a class?
  2. Remove operator !=, operator ==, GetHashCode and Equals and make compiler generate them for me (the way C++ would do it).

How can I do that?

Additional info:

I’m already aware that clone method offers functionality similar to ICloneable. I have no need to support ICLoneable at the moment.

Usage pattern:
Those items are going to be stuffed into several lists, arrays and hashtables, compared to each other, assign is going to be called often.

Advice?

public class MaterialParam<Value> where Value: System.IEquatable<Value>{
    public string name = "";
    public string shaderValueName = "";
    public Value value;// = Value();
    public void assignFrom(MaterialParam<Value> other){
        name = other.name;
        shaderValueName = other.shaderValueName;
        value = other.value;
    }

    public MaterialParam<Value> clone(){
        var result = new MaterialParam<Value>();
        result.assignFrom(this);
        return result;
    }

    public override bool Equals(object obj){
        if(!(obj is MaterialParam<Value>))
            return false;

        MaterialParam<Value> other = (MaterialParam<Value>)obj;
        if((System.Object)other == null){
            return false;
        }           

        return (this.name == other.name) && 
            (this.shaderValueName == other.shaderValueName) &&
                (this.value.Equals(other.value));
    }

    public override int GetHashCode(){
        return name.GetHashCode() ^ shaderValueName.GetHashCode() 
            ^ value.GetHashCode();
    }

    public static bool operator !=(MaterialParam<Value> a, MaterialParam<Value> b){
        return !(a == b);
    }

    public static bool operator ==(MaterialParam<Value> a, MaterialParam<Value> b){
        return a.Equals(b);
    }
};

public class MaterialRangedParam<Value>: MaterialParam<Value> where Value: System.IEquatable<Value>{
    public Value minValue;// = Value();
    public Value maxValue;// = Value();
    public void assignFrom(MaterialRangedParam<Value> other){
        base.assignFrom(other);
        minValue = other.minValue;
        maxValue = other.maxValue;
    }

    new public MaterialRangedParam<Value> clone(){
        var result = new MaterialRangedParam<Value>();
        result.assignFrom(this);
        return result;
    }

    public override bool Equals(object obj){
        if (!base.Equals(obj))
            return false;

        if(!(obj is MaterialRangedParam<Value>))
            return false;

        MaterialRangedParam<Value> other = (MaterialRangedParam<Value>)obj;
        if((System.Object)other == null){
            return false;
        }           

        return (minValue.Equals(other.minValue)) && (maxValue.Equals(other.maxValue));
    }

    public override int GetHashCode(){
        return base.GetHashCode() ^ minValue.GetHashCode() ^ maxValue.GetHashCode();
    }

    public static bool operator !=(MaterialRangedParam<Value> a, MaterialRangedParam<Value> b){
        return !(a == b);
    }

    public static bool operator ==(MaterialRangedParam<Value> a, MaterialRangedParam<Value> b){
        return a.Equals(b);
    }
}

Solution

This code looks very Java-like in its style and implementation. Maybe C#1.0 at its best. There are a number of stylistic and idiomatic changes I’d make to make these two classes “play well” in the C# / .NET world of 2015. Without going into exhaustive detail, I employed object immutability, parameterized constructors, implementing IClonaeble, utilizing properties, correctly implementing Equals and ==.

With regards to the two things that you’d like to do, sorry, neither are possible (nor really desirable) with C# for various reasons that you can find on MSDN.

The code:

public class MaterialParam<TValue> where TValue : IEquatable<TValue>, ICloneable
{
    public readonly string name = string.Empty;

    public readonly string shaderValueName = string.Empty;

    public readonly TValue value; // = Value();

    public MaterialParam()
    {
    }

    public MaterialParam(string name, string shaderValueName, TValue value)
    {
        if (name == null)
        {
            throw new ArgumentNullException("name");
        }

        if (shaderValueName == null)
        {
            throw new ArgumentNullException("shaderValueName");
        }

        if (value == null)
        {
            throw new ArgumentNullException("value");
        }

        this.name = name;
        this.shaderValueName = shaderValueName;
        this.value = value;
    }

    public MaterialParam(MaterialParam<TValue> other)
    {
        if (other == null)
        {
            throw new ArgumentNullException("other");
        }

        this.name = other.name;
        this.shaderValueName = other.shaderValueName;
        this.value = other.value;
    }

    public string Name
    {
        get
        {
            return this.name;
        }
    }

    public string ShaderValueName
    {
        get
        {
            return this.shaderValueName;
        }
    }

    public TValue Value
    {
        get
        {
            return this.value;
        }
    }

    public static bool operator ==(MaterialParam<TValue> a, MaterialParam<TValue> b)
    {
        if (ReferenceEquals(a, b))
        {
            return true;
        }

        if (((object)a == null) || ((object)b == null))
        {
            return false;
        }

        return a.Equals(b);
    }

    public static bool operator !=(MaterialParam<TValue> a, MaterialParam<TValue> b)
    {
        return !(a == b);
    }

    public MaterialParam<TValue> Clone()
    {
       return new MaterialParam<TValue>(this);
    }

    public override bool Equals(object obj)
    {
        if (!(obj is MaterialParam<TValue>))
        {
            return false;
        }

        var other = (MaterialParam<TValue>)obj;

        return (this.name == other.Name)
            && (this.shaderValueName == other.ShaderValueName)
            && this.value.Equals(other.Value);
    }

    public override int GetHashCode()
    {
        return this.name.GetHashCode()
            ^ this.shaderValueName.GetHashCode()
            ^ this.value.GetHashCode();
    }
}

public class MaterialRangedParam<TValue> : MaterialParam<TValue> where TValue : IEquatable<TValue>, ICloneable
{
    public readonly TValue minValue; // = Value();

    public readonly TValue maxValue; // = Value();

    public MaterialRangedParam(TValue minValue, TValue maxValue)
    {
        if (minValue == null)
        {
            throw new ArgumentNullException("minValue");
        }

        if (maxValue == null)
        {
            throw new ArgumentNullException("maxValue");
        }

        this.minValue = minValue;
        this.maxValue = maxValue;
    }

    public MaterialRangedParam(MaterialRangedParam<TValue> other) : base(other)
    {
        if (other == null)
        {
            throw new ArgumentNullException("other");
        }

        this.minValue = other.MinValue;
        this.maxValue = other.MaxValue;
    }

    public TValue MinValue
    {
        get
        {
            return this.minValue;
        }
    }

    public TValue MaxValue
    {
        get
        {
            return this.maxValue;
        }
    }

    public static bool operator ==(MaterialRangedParam<TValue> a, MaterialRangedParam<TValue> b)
    {
        if (ReferenceEquals(a, b))
        {
            return true;
        }

        if (((object)a == null) || ((object)b == null))
        {
            return false;
        }

        return a.Equals(b);
    }

    public static bool operator !=(MaterialRangedParam<TValue> a, MaterialRangedParam<TValue> b)
    {
        return !(a == b);
    }

    public new MaterialRangedParam<TValue> Clone()
    {
        return new MaterialRangedParam<TValue>(this);
    }

    public override bool Equals(object obj)
    {
        if (!(obj is MaterialRangedParam<TValue>))
        {
            return false;
        }

        var other = (MaterialRangedParam<TValue>)obj;

        return base.Equals(other) && this.minValue.Equals(other.MinValue) && this.maxValue.Equals(other.MaxValue);
    }

    public override int GetHashCode()
    {
        return base.GetHashCode() ^ this.minValue.GetHashCode() ^ this.maxValue.GetHashCode();
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *