Reactgular
Reactgular

Reputation: 54801

Most effective way to create a class that has a small variance in derived classes

I have a class that contains a lot of functionality called Record. In the system there exist two basic types of records that have a primary key that is uint, and those that have a primary key of Guid.

The Record class contains all the features that I need except for one property. Record.ID allows the developer to read the current primary key as the correct type (uint or Guid).

To achieve this I had to create three classes like this (simplified example):

class Record {
    ... lots of code ...
}

class RecordInt : Record {
     public uint ID { get; set; }
}

class RecordGuid : Record {
     public Guid ID { get; set; }
}

This works ok, but it introduced a problem where data models can't just create a generic Record instance. They have to know if derive classes are expecting RecordInt or RecordGuid. So I added a template argument to models like this.

class abstract class Model<T> : were T : Record, new()
{
   ... lots of generic code that uses T ...

   // example
   public List<T> Find(...) { 
      .. find a record ...
      return new List<T>(); 
   }
}

This has caused me more problems than the benefit of simply having two Record types. Now when I instantiate a model the code has to know what type of Record is required. If that takes place in a section that should be generic, then I have to add another template argument.

I found after a while there started to be a lot of methods using template arguments. Just so that type could be passed to Model<RecordGuid>, and sometimes I have to pass the template argument down a chain of several method calls befor it gets to code that really needs it.

Is there a more effective pattern then what I'm doing?

EDIT:

One reason why Model is using a template argument is that it needs to have methods that return collections of Record. In C# you can't cast List<Record> to List<RecordGuid> so I had to use a template argument for the return type as List<T>.

Upvotes: 0

Views: 141

Answers (1)

Reactgular
Reactgular

Reputation: 54801

I decided to create a new class to represent primary keys, and have that class control the Type state for that ID.

This would allow me to create just a single class to represent a Record while that class could support either uint or Guid primary keys. It also means that the Model class no longer required template arguments which resulted in other source code dependant upon Model to also not require template arguments.

Here is my PrimaryValue class that I'm using.

/// <summary>
/// Used to store the value of the primary key for a table.
/// </summary>
public sealed class PrimaryValue
{
    /// <summary>
    /// The raw value
    /// </summary>
    private object _value;

    /// <summary>
    /// The required type.
    /// </summary>
    private Type _type;

    /// <summary>
    /// True if a Guid type.
    /// </summary>
    public bool IsGUID
    {
        get
        {
            return _type == typeof(Guid);
        }
    }

    /// <summary>
    /// Type if a uint type.
    /// </summary>
    public bool IsInteger
    {
        get
        {
            return _type == typeof(uint);
        }
    }

    /// <summary>
    /// True if the value is empty.
    /// </summary>
    public bool Empty
    {
        get
        {
            if (_type == typeof(uint))
            {
                return (uint)_value == 0;
            }
            return (Guid)_value == Guid.Empty;
        }
    }

    /// <summary>
    /// Constructor
    /// </summary>
    public PrimaryValue(Type pType)
    {
        _type = pType;
        if (pType == typeof(uint))
        {
            _value = 0;
        }
        else if (pType == typeof(Guid))
        {
            _value = Guid.Empty;
        }
        else
        {
            throw new ModelException("Type not supported by PrimaryValue.");
        }
    }

    /// <summary>
    /// UINT constructor.
    /// </summary>
    public PrimaryValue(uint pValue)
    {
        _value = pValue;
        _type = typeof(uint);
    }

    /// <summary>
    /// GUID constructor
    /// </summary>
    public PrimaryValue(Guid pValue)
    {
        _value = pValue;
        _type = typeof(Guid);
    }

    /// <summary>
    /// Copy constructor.
    /// </summary>
    public PrimaryValue(PrimaryValue pValue)
    {
        _value = pValue._value;
        _type = pValue._type;
    }

    public void set(PrimaryValue pValue)
    {
        if (_type == pValue._type)
        {
            _value = pValue._value;
            return;
        }

        throw new ModelException("PrimaryValues are not of the same type.");
    }

    /// <summary>
    /// Assigns a UINT value.
    /// </summary>
    public void set(uint pValue)
    {
        if (_type == typeof(uint))
        {
            _value = pValue;
            return;
        }

        throw new ModelException("PrimaryValue is not a UINT type.");
    }

    /// <summary>
    /// Assigns a GUID value.
    /// </summary>
    public void set(Guid pValue)
    {
        if (_type == typeof(Guid))
        {
            _value = pValue;
            return;
        }

        throw new ModelException("PrimaryValue is not a GUID type.");
    }

    /// <summary>
    /// Returns the raw value.
    /// </summary>
    public object get()
    {
        return _value;
    }

    /// <summary>
    /// Gets the ID as UINT.
    /// </summary>
    public uint ToInteger()
    {
        if (_type != typeof(uint))
        {
            throw new ModelException("PrimaryValue is not a UINT type.");
        }
        return (uint)_value;
    }

    /// <summary>
    /// Gets the ID as GUID.
    /// </summary>
    public Guid ToGuid()
    {
        if (_type != typeof(Guid))
        {
            throw new ModelException("PrimaryValue is not a GUID type.");
        }
        return (Guid)_value;
    }

    /// <summary>
    /// Checks if two IDs are equal.
    /// </summary>
    public static bool operator ==(PrimaryValue A, PrimaryValue B)
    {
        if (A._value.GetType() == B._value.GetType())
        {
            return A._value == B._value;
        }

        throw new ModelException("Can not compare PrimaryValues of different types.");
    }

    /// <summary>
    /// Checks if two IDs are not equal.
    /// </summary>
    public static bool operator !=(PrimaryValue A, PrimaryValue B)
    {
        if (A._value.GetType() == B._value.GetType())
        {
            return A._value != B._value;
        }

        throw new ModelException("Can not compare PrimaryValues of different types.");
    }

    /// <summary>
    /// Convertion to UINT.
    /// </summary>
    public static implicit operator uint(PrimaryValue A)
    {
        return A.ToInteger();
    }

    /// <summary>
    /// Convertion to Guid.
    /// </summary>
    public static implicit operator Guid(PrimaryValue A)
    {
        return A.ToGuid();
    }

    /// <summary>
    /// Convertion to string.
    /// </summary>
    public static implicit operator string(PrimaryValue A)
    {
        return A._value.ToString();
    }

    /// <summary>
    /// Convert to string.
    /// </summary>
    public override string ToString()
    {
        return _value.ToString();
    }

    /// <summary>
    /// Hashcode
    /// </summary>
    public override int GetHashCode()
    {
        return _value.GetHashCode();
    }
}

Upvotes: 2

Related Questions