2

I have a C# method where I am trying to use generics as:

unsafe private void PerformWindowLevel<T>(int lower, int upper, ref T[] pixels)

Somewhere along the function I have the following line:

float shift = ... // some value
float scale = ... // some value
float val = ((float)pixels[i] + shift) * scale;

Here I get the error Cannot convert type 'T' to 'float'.

The method is called as:

PerformWindowLevel<byte>(10, 100, ref pixels);

So I am converting a byte type to float which should be possible. pixels is a byte array declared as public byte[] pixels; and filled with valid values.

Luca
  • 10,458
  • 24
  • 107
  • 234
  • why do you want to use generic if you uses this method with ? – DdarkSideE Jul 12 '17 at 09:51
  • 1
    How about `float val = (Convert.ToSingle((object)pixels[i] + shift)) * scale`? Applying generic constraint only restricts to certain type, if you want broader `T` types use conversion method (note that type parameter T can only converted to an object). – Tetsuya Yamamoto Jul 12 '17 at 09:55
  • @TetsuyaYamamoto This complains that I cannot use the (+) operator on object and float... – Luca Jul 12 '17 at 09:59
  • @DdarkSideE The method will also be used with other types like UInt16 etc. They will always be convertible to floats. – Luca Jul 12 '17 at 09:59
  • 1
    Seems I putting `Convert.ToSingle` method in wrong scope: `(Convert.ToSingle((object)pixels[i]) + shift)`. The `object` need to cast as `float` and perform addition with another `float`. – Tetsuya Yamamoto Jul 12 '17 at 10:03
  • @TetsuyaYamamoto This worked! Want to write it as an answer and I will accept it. – Luca Jul 12 '17 at 10:27

4 Answers4

7

The problem is that T doesn´t know anything about what it might be in your calling code, it´s just a generic type-parameter, that could also be string or whatever which you (apparently) can´t cast to float. You´d need a generic constraint:

unsafe private void PerformWindowLevel<T>(int lower, int upper, ref T[] pixels) where T: float

This still doesn´t work as you can´t use a struct (which float is) as generic type-parameter. Only interfaces or classes are allowed.

However this would only allow float to be a valid generic parameter making the term generic quite - well - ungeneric. This is why generics are completely wrong in your case. When only one or two types are possible you should create a concrete method (or an overload) for that specific type. Generics on the other side state that every type (that satisfies the generic constraint which is indicated by a where) is possible, not just one.

InBetween
  • 32,319
  • 3
  • 50
  • 90
MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
  • It does not work, because "A type used as a constraint must be an interface, a non-sealed class or a type parameter." – DdarkSideE Jul 12 '17 at 09:56
4

You need to write a Seperate TypeConverter as at Compile Time the Compiler is unaware of Type T and whether it can be converted to float .

you can use Convert.ChangeType Method to Convert

public class ConvertTypeClass<T1,T2>
    {
        public T1 ConvertMethod(T2 val)
        {
            return (T1) (Convert.ChangeType(val, typeof(T1)));
        }
    } 
Test12345
  • 1,625
  • 1
  • 12
  • 21
4

Cannot convert type 'T' to 'float' indicates that the compiler simply doesn't know exactly what kind of data type that generic type parameter T currently has. Any attempt to cast T to other type except for object (as base class of all objects) will result InvalidCastException.

Hence, based from how generic type parameter works as explained above, the code can be written as this:

unsafe private void PerformWindowLevel<T>(int lower, int upper, ref T[] pixels)
{
    float shift = 3.0F; // some value
    float scale = 5.0F; // some value
    float val = (Convert.ToSingle((object)pixels[i]) + shift) * scale;
}

or include a check for byte data type before performing cast:

unsafe private void PerformWindowLevel<T>(int lower, int upper, ref T[] pixels)
{
    if (typeof(T) == typeof(byte))
    {
        float shift = 3.0F; // some value
        float scale = 5.0F; // some value
        float val = (Convert.ToSingle((object)pixels[i]) + shift) * scale;
    }
}

Note that the generic itself should be completely independent of the type, so if type parameter T intended to just accept byte just use ref byte[] pixels instead.

NB: About the message A type used as a constraint must be an interface, a non-sealed class or a type parameter, it can be explained where float (System.Single) itself is just a struct, which is implicitly sealed. A struct or sealed class can't be used as generic type parameter constraint, but you can constrain that T is a struct. Currently valid constraints should be like this:

void GenericMethod<T>() where T: class // reference type constraint

void GenericMethod<T>() where T: struct {} // value type constraint

void GenericMethod<T>() where T: new() // public parameterless constructor constraint

void GenericMethod<T>() where T: IConvertible // interface constraint

void GenericMethod<T>() where T: BaseClass // base class constraint

More info: Constraints on Type Parameters

Similar issues:

Cannot implicitly convert type 'T' to 'Int'

Value of type 'T' cannot be converted to

Tetsuya Yamamoto
  • 24,297
  • 8
  • 39
  • 61
2

This is a known problem given that the basic arithmetic types do not have a common interface (e.g. INumeric) which you could use to restrict your method, via where T : INumeric.

There is, however, a slightly clumsy workaround, which does compile! If you include your code within a typeof condition, the compiler is happy. For example:

private void PerformWindowLevel<T>(int lower, int upper, ref T[] pixels) 
{
    if ((typeof(T) == typeof(byte)) || (typeof(T) == typeof(int)))
    {
        float shift = 1;
        float scale = 2;
        float val = ((float)(object)pixels[0] + shift) * scale;
    }  
}

All you need to do, is to include in your surrounding if condition, as many types as you need! As I say a bit clumsy, but it works!

Jonathan Willcock
  • 5,012
  • 3
  • 20
  • 31