4

I'm developing a class library with C#, .NET Framework 4.7.1 and Visual Studio 2017 version 15.6.3.

I have this code:

public static T Add<T, K>(T x, K y)
{
    dynamic dx = x, dy = y;
    return dx + dy;
}

If I use with this code:

genotype[index] = (T)_random.Next(1, Add<T, int>(_maxGeneValue, 1));

genotype is a T[].

I get the error:

Argument 2: cannot convert from 'T' to 'int'

But if I change the code:

public static K Add<T, K>(T x, K y)
{
    dynamic dx = x, dy = y;
    return dx + dy;
}

I get the error:

CS0030 Cannot convert type 'int' to 'T'

When I get this error T is a byte.

How can I fix this error? Or maybe I can change random.Next to avoid doing the Add.

The minimal code:

public class MyClass<T>
{
    private T[] genotype;
    private T _maxGeneValue;

    public void MinimalCode()
    {
        Random _random = new Random();
        genotype = new T[] {0, 0, 0};
        int index = 0;
        _maxGeneValue = 9;

        genotype[index] = (T)_random.Next(1, Add<T, int>(_maxGeneValue, 1));
    }
}

It is a generic class because the genotype array can be of bytes or integers or floats. And I use the random to generate random values between 1 and _maxGeneValue. I need to add one because the maxValue in Random is exclusive.

The elements in the genotype array could any of the built-in types, as far as I know numerics. And I don't want to create a class for each of the types I'm going to use (and use the biggest one in array declaration, i.e. long[], is a waste of space).

VansFannel
  • 45,055
  • 107
  • 359
  • 626
  • 2
    Please post a [mcve]. – Lasse V. Karlsen Mar 23 '18 at 10:42
  • 2
    Also, please explain what exactly you want the randomness to do here, if the underlying T's are really numbers, why are you trying to treat them in a generic way? – Lasse V. Karlsen Mar 23 '18 at 10:44
  • I have updated the question. – VansFannel Mar 23 '18 at 10:50
  • 6
    *T is a generic class because the genotype array can be of bytes or integers or floats.*. And there lies your error. Thats **not generic**. Generic means *infinite* types are allowed, 3 is pretty darn far from infinite. The solution to your problem is overloading `Add` or defining one single method that accepts a type to which all possible expected types can be implicitly cast. – InBetween Mar 23 '18 at 10:51
  • Why do you believe it should be generic? Why do you believe it should work? Why the assignment to dynamics? – Icepickle Mar 23 '18 at 10:56
  • Try the following changes: `genotype = new T[3];`, `_maxGeneValue = (T)Convert.ChangeType(9, typeof(T));`, and `genotype[index] = (T)Convert.ChangeType(_random.Next(1, Convert.ToInt32(Add(_maxGeneValue, 1))), typeof(T));` – Lasse V. Karlsen Mar 23 '18 at 10:57
  • 1
    It's still not going to be "generic" in the sense that `MyCode` will fail horribly, but it may be enough for you. I would probably add a constraint that `T` must be a `struct`, and also verify in the constructor that `T` is one of the supported types. – Lasse V. Karlsen Mar 23 '18 at 10:58
  • 1
    There is no way in C# to express that generic T is "byte, integer or float" or even "T is numeric type". – Evk Mar 23 '18 at 11:14
  • @Icepickle Look: https://stackoverflow.com/a/9338514/68571. I have updated the question with more details. – VansFannel Mar 23 '18 at 11:23

1 Answers1

6

T is a generic class because the genotype array can be of bytes or integers or floats

That is not what generic means. Generic means that there is an unbound number of valid types. You only have 3, that does not qualify for a generic solution at all.

A major lesson learned here is: if the type system fights against you, stop and think it over, you are probably doing something wrong; the cast to dynamic should have been a huge red flag.

The solution to your problem is to simply overload. You have 3 valid types: byte, int and float. I'm guessing you want to avoid losing information so the following should hold:

  1. If adding integral types, use the "smallest" possible type (this can be potentially "unsafe" in the sense that it can overflow).
  2. If one of the operands is a float, use floating point arithmetics.

OK, so we need:

public byte Add(byte left, byte right) { .... }
public int Add(int left, int right) { .... }
public float Add(float left, float right) { .... }

And now think about something else: do you want the sum of two bytes to overflow? Or do you want to return an int? What I mean is: what should byte 250 + byte 10 return? A byte or an int? If its an int, remove the first overload. Now think about the same issue with ints. If you don't want any overflows, then remove the second overload too.

InBetween
  • 32,319
  • 3
  • 50
  • 90
  • Ok, I think I have misunderstood the meaning of Generic. Thanks. But the elements in the `genotype` array could any of the built-in types, as far as I know numerics. And I don't want to create a class for each of the types I'm going to use (and use the biggest one in array declaration is a waste of space). – VansFannel Mar 23 '18 at 11:09
  • But that (adding `Add` overloads) won't help OP, because he has `MyClass`. – Evk Mar 23 '18 at 11:09
  • 1
    @Evk but `Class` when `T` can only be numeric types is wrong to begin with. Generics is not a good fit for this scenario. There is no `T: Numeric` constraint to make this reasonably viable. – InBetween Mar 23 '18 at 11:10
  • True, but that's another story. I mean in current situation, adding multiple `Add` won't help and won't fix anything. Making 3 separate classes would fix, but that's not what OP wants. – Evk Mar 23 '18 at 11:11