1

(Question at the bottom)

I have a class which I need to be used like:

float [] floatArray=new float[1024];

Foo<float> var1=floatArray; // creates foo, assigns the array to a field

var1.compute();

and

Foo<float> floatArray2 = new Foo<float>(1024);

Foo<float> var2=floatArray2; // creates foo, copies its internal array 

var2.compute();

Then I thought I could use two implicit conversions, one for arrays, one for non-arrays.

I could manage to do first version with:

    public static implicit operator Foo<T> (T[] b)
    {
        if (typeof(T) == typeof(int) ||
            typeof(T) == typeof(uint) ||
            typeof(T) == typeof(byte) ||
            typeof(T) == typeof(char) ||
            typeof(T) == typeof(double) ||
            typeof(T) == typeof(long) ||
            typeof(T) == typeof(float))
        {
            Foo<T> foo = new Foo<T>();
            foo.arr = b;

            return foo;
        }
        else
        {
            throw new NotImplementedException();
        }
    }

but this has a lot of checks in it and I tried to add constraint in class declaration like:

   public class Foo<T> where T:typeof(float),typeof(int)
   {
      ...
   }

but it made the compiler complain as "int is not a valid constraint".

In the second case, I needed to exclude arrays, I tried this:

    public static implicit operator Foo<T> (Foo<T>  b)
    {         
            Foo<T> foo = new Foo<T>();
            foo.arr = b.arr;

            return foo;
    }

but this time compiler says "cant take enclosing type and convert to enclosing type" and underlines operator Foo<T>.

But these actually work:

    public static implicit operator Foo<T> (Foo<float>  b)
    public static implicit operator Foo<float> (Foo<T>  b)

and I don't want to cross-convert int type to float nor float to int. Just T to same T(not all T to all T).

Question: How to constrain the implicit conversion to only arrays of primitive numbers like float,byte,double,long,int and 1-2 custom classes without adding too many typechecks in the method body(custom classes are generic with float int byte...)? (where keyword doesn't work on method <T> definition)


Foo already implements IList but IList could be anything like a Bar[] right? So I can't use interface to narrow the search I assume. Also I don't want the = to do a reference assignment for Foo, but create a new one(or just use the current, would be better) and copy its internal array, just like a struct, but still have many advantages of a class(inheritance, ...).

huseyin tugrul buyukisik
  • 11,469
  • 4
  • 45
  • 97

2 Answers2

2

Generic constraints on C# are somewhat limited, you can restrict T to be a class or a struct, but you can't restrict it to be only one of the speficied types like, int, float, double. So in order to solve this issue you are gonna either need to have a manual if check like you did or you need to add overloaded implicit operators for each type. Although it requires more work I would recommend overloading solution because it's more type safe.

On the second issue, adding an implicit conversion from a type to the same type itself is not allowed. The relevant part from the specification explains it in detail (C# Spec 5.0 10.10.3. Conversion Operators):

For a given source type S and target type T, if S or T are nullable types, let S0 and T0 refer to their underlying types, otherwise S0 and T0 are equal to S and T respectively. A class or struct is permitted to declare a conversion from a source type S to a target type T only if all of the following are true:

  • S0 and T0 are different types.
  • Either S0 or T0 is the class or struct type in which the operator declaration takes place.

  • Neither S0 nor T0 is an interface-type.

  • Excluding user-defined conversions, a conversion does not exist from S to T or from T to S.

For the purposes of these rules, any type parameters associated with S or T are considered to be unique types that have no inheritance relationship with other types, and any constraints on those type parameters are ignored. In the example

class C<T> {...}
class D<T>: C<T>
{
  public static implicit operator C<int>(D<T> value) {...}        // Ok
  public static implicit operator C<string>(D<T> value) {...} // Ok
  public static implicit operator C<T>(D<T> value) {...}      // Error
}

the first two operator declarations are permitted because, for the purposes of §10.9.3, T and int and string respectively are considered unique types with no relationship. However, the third operator is an error because C is the base class of D.

Note: When you need to manually check the type of a generic parameter it usually indicates a design flaw in ur code, so you should think again about making the Foo generic. Does it really have to be generic? I'm assuming this is not the only place where you check the typeof T, you are probably gonna need to check the type again where you actually use the array of T and manually cast the items to that type in order to operate with them.

Selman Genç
  • 100,147
  • 13
  • 119
  • 184
  • It has to be generic to act as an array and a custom class at the same time to hide many details from client developer. Trying to make it look like an array with many functionalities hidden(gpgpu) and be usable in pure C# codes(implicit array extraction from field) and have number types or custom class number types as an inner array. – huseyin tugrul buyukisik Mar 24 '17 at 16:12
1

You can have limit T to struct this will let you already quite good compile time support.
Then, create a private constructor and make all the type checking there, including the assignment of the array.
There is no way that i know of to change the reference assignment of the same type, so that requirement cannot be met.

public void GenericImplicitTest()
{
    int[] ints = new int[4];
    char[] chars = new char[5];
    Foo<int> foo = ints;
    Foo<char> foo_s = chars;
    // Foo<float> f = ints;  <-- will not compile

}

class Foo<T> where T: struct 
{
    private readonly T[] arr;

    private Foo(T[] arr)
    {
        if (typeof (T) != typeof (int) && typeof (T) != typeof (uint) && typeof (T) != typeof (byte) && typeof (T) != typeof (char) &&
            typeof (T) != typeof (double) && typeof (T) != typeof (long) && typeof (T) != typeof (float))
            throw new NotSupportedException($"{typeof (T)} is not supported");

        this.arr = arr;
    }

    public static implicit operator Foo<T>(T[] b)
    {
        Foo<T> foo = new Foo<T>(b);
        return foo;
    }
}

If you don't want to set the array via the constructor then create a private static Create method;

Ofir Winegarten
  • 9,215
  • 2
  • 21
  • 27
  • Do you mean using `not equal && not equal &&` is more concrete and quicker in runtime than `equal || equal || ..` ? Should I benchmark a lookup table for this? Would it be optimized away to same performance? I mean saving some different `T` as an array of booleans to be used in a dictionary. Maybe JIT makes same thing in behind? – huseyin tugrul buyukisik Mar 24 '17 at 16:17
  • No, its just a habit of mine to return or throw as early as possible. So there's no need for else block here. – Ofir Winegarten Mar 24 '17 at 17:24