0

I wanted to create a generic (sic) solution to have a class with a bunch of properties. These properties should be simple types (bool, int, float etc) or complex types (vector, colour etc.). They should all have a means to parse them from text to their type.

The one complex type I'm going to use here is Vector:

public class Vector
{
    private float _x, _y, _z;

    public Vector(float x, float y, float z)
    {
        _x = x;
        _y = y;
        _z = z;
    }

    public Vector() : this(0.0f, 0.0f, 0.0f)
    {

    }

    public float X
    {
        get { return _x; }
        set { _x = value; }
    }

    public float Y
    {
        get { return _y; }
        set { _y = value; }
    }

    public float Z
    {
        get { return _z; }
        set { _z = value; }
    }
}

Here's my base class for parameter, which just gives it a name:

public class Parameter
{
    protected string _name;

    public Parameter() : this("untitled")
    {

    }

    public Parameter(string name)
    {
        _name = name;
    }

    public string Name => _name;
}

Here's the derived class which adds a value of generic type TType, which has a constraint:

public class Parameter<TType> : Parameter where TType : ParameterValue<TType>
{
    public Parameter(string name, TType value) : base(name)
    {
        Value = value;
    }

    public TType Value { get; set; }
}

Now here's the ParameterValue generic class which ensures all TType objects have a parse function

public abstract class ParameterValue<TType>
{
    protected TType _value;

    public ParameterValue(TType value)
    {
        _value = value;
    }

    public TType Value
    {
        get { return _value; }
        set { _value = value; }
    }

    public abstract void Parse(string text);
}

Here's a definition for string:

public class StringValue : ParameterValue<string>
{
    public StringValue(string value) : base(value)
    {

    }

    public override void Parse(string text)
    {
        _value = text;
    }
}

Here's a definition for Vector:

public class VectorValue : ParameterValue<Vector>
{
    public VectorValue(Vector value) : base(value)
    {

    }

    public override void Parse(string text)
    {
        var tokens = text.Split(',');

        var x = float.Parse(tokens[0]);
        var y = float.Parse(tokens[1]);
        var z = float.Parse(tokens[2]);

        _value = new Vector(x, y, z);
    }
}

Here's my manager class which contains all the parameters:

public class ParameterManager
{
    private Dictionary<string, Parameter> _parameters;

    public ParameterManager()
    {
        _parameters = new Dictionary<string, Parameter>();
    }

    public void AddParameter<TType>(string name, TType value) where TType : ParameterValue<TType>
    {
        _parameters[name] = new Parameter<TType>(name, value);
    }

    public TType FindParameterValue<TType>(string name) where TType : ParameterValue<TType>
    {
        var parameter = _parameters[name];
        var parameterTyped = parameter as Parameter<TType>;
        return parameterTyped?.Value;
    }
}

Now, if I create a class that uses ParamaterManager, I hit problems:

public class Thing
{
    private ParameterManager _parameters;

    public Thing()
    {
        _parameters = new ParameterManager();

        _parameters.AddParameter("name", new StringValue("untitled"));
        _parameters.AddParameter("position", new VectorValue(new Vector()));
    }
}

The two lines adding parameters "name" and "position" throw errors:

1>...\Thing.cs(11,13,11,37): error CS0311: The type 'ParameterProblem.StringValue' cannot be used as type parameter 'TType' in the generic type or method 'ParameterManager.AddParameter<TType>(string, TType)'. There is no implicit reference conversion from 'ParameterProblem.StringValue' to 'ParameterProblem.ParameterValue<ParameterProblem.StringValue>'.
1>...\Thing.cs(12,13,12,37): error CS0311: The type 'ParameterProblem.VectorValue' cannot be used as type parameter 'TType' in the generic type or method 'ParameterManager.AddParameter<TType>(string, TType)'. There is no implicit reference conversion from 'ParameterProblem.VectorValue' to 'ParameterProblem.ParameterValue<ParameterProblem.VectorValue>'.

How do I get this to do what I wanted?

0

2 Answers 2

1
where TType : ParameterValue<TType>

This is a recursive generic constraint, which will simplify to TType : ParameterValue<XYZParameterValue> where XYZParameterValue : ParameterValue<TType> which is not what you want, because in your case the actual type (e.g. string) does not inherit its corresponding ParameterValue (ParameterValue<string>).

Your generic constraints would work when using a generic interface/base class which is implemented/inherited by the same type which it is generic over, like the IComparable<T> interface which is implemented by the type T (i.e. System.String : IComparable<System.String>).

Instead, I'd do the following:

public class Parameter<T> : Parameter
{
    public Parameter(string name, ParameterValue<T> value) : base(name)
    {
        Value = value;
    }

    public ParameterValue<T> Value { get; set; }
}

You'd have to change ParameterManager methods to a similar form too:

public void AddParameter<T>(string name, ParameterValue<T> value)
{
    _parameters[name] = new Parameter<TType>(name, value);
}

public ParameterValue<T> FindParameterValue<T>(string name) 
{
    var parameter = _parameters[name];
    var parameterTyped = parameter as Parameter<TType>;
    return parameterTyped?.Value;
}

Side Note: Naming the type constraint TType doesn't make any sense by general conventions since the T prefix in a type parameter means "type" and so T would be enough.

Sign up to request clarification or add additional context in comments.

1 Comment

I'm overthinking it again
1

Your constraint should be replaced with a change in the second parameter type:

public void AddParameter<TType>(string name, ParameterValue<TType> value)

The calls should be done like this:

_parameters.AddParameter<string>("name", new StringValue("untitled"));
_parameters.AddParameter<Vector>("position", new VectorValue(new Vector()));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.