简体   繁体   中英

Initialize a non-generic object with a generic object

Sometimes I do not get those T s of C# Generics right. I have a generic struct

public struct ValueWithUnit<T>
{
    public ValueWithUnit(T _value, Unit _unit)
    {
        Unit = _unit;
        Value = _value;
    }
    public Unit Unit { get; }
    public T Value { get; }
}

( Unit is an enum , T should be numeric, but there is no constraint available for that purpose).

For WCF I need a non-generic version of that, with T being double . So I thought of:

public struct DoubleValueWithUnit 
{
    public DoubleValueWithUnit(double _value, Unit _unit)
    {
        Unit = _unit;
        Value = _value;
    }
    public DoubleValueWithUnit(ValueWithUnit<T> _valueWithUnit)
    {
        Unit = _valueWithUnit.Unit;
        Value = Convert.ToDouble(_valueWithUnit.Value);
    }
    public Unit Unit { get; set; }
    public double Value { get; set; }
}

But the second constructor does not compile: error CS0246: The type or namespace name 'T' could not be found ... and Convert.ToDouble complains with Cannot resolve method 'ToDouble(T)' Candidates are...

I know I can add a conversion method to the generic class:

    public DoubleValueWithUnit ToDoubleValueWithUnit()
    {
        return new DoubleValueWithUnit(Convert.ToDouble(Value), Unit);
    }

That works. But is there any possibility to add a constructor with a generic parameter to a non-generic class/struct?

I don't think this constructor should exist at all:

public DoubleValueWithUnit(ValueWithUnit<T> _valueWithUnit)
{
    Unit = _valueWithUnit.Unit;
    Value = Convert.ToDouble(_valueWithUnit.Value);
}

Why do you want to convert a ValueWithUnit<T> to a DoubleValueWithUnit ? With some values of T , this does not make sense. How do you convert a BinaryFormatter to double ? Or a Form to double ? These simply should not be allowed at compile time.

So you either do this:

public DoubleValueWithUnit(ValueWithUnit<double> _valueWithUnit)
{
    Unit = _valueWithUnit.Unit;
    Value = _valueWithUnit.Value;
}

Or remove the constructor all together.

In the second example, T is simply not defined. So you cannot use T in the context of that struct.

Just remove this constructor:

public DoubleValueWithUnit(ValueWithUnit<T> _valueWithUnit)

Since you would like to convert anything passed to Double, define a constructor taking as input an object. In the constructor try to cast and throw an exception if the object is not convertible.

public DoubleValueWithUnit(object obj, Unit unit)
{
    Unit = unit;
    try
    {
       Value = Convert.ToDouble( obj );
    }
    catch( Exception )
    {
       throw new ArgumentException("Cannot convert to double", nameof(obj) );
    }        
}

My current solution is to have the struct implementing a generic interface which in turn inherits from a non-generic interface:

public struct ValueWithUnit<T> : IValueWithUnit<T> {...}

public interface IValueWithUnit<out T> : IValueWithUnit // where T: number
{
    new T Value { get; }
}
public interface IValueWithUnit
{
    object Value { get; }
    Unit Unit { get; }
}

Now, I can pass a ValueWithUnit<T> into the (modified) constructor:

public DoubleValueWithUnit(IValueWithUnit _valueWithUnit)
{
    Unit = _valueWithUnit.Unit;
    Value = Convert.ToDouble(_valueWithUnit.Value);
}

Still I am not sure if there are better solutions possible.

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