huseyin tugrul buyukisik
huseyin tugrul buyukisik

Reputation: 11926

Implicit conversion with type constraints using "where" to exclude(or include) types

(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, ...).

Upvotes: 1

Views: 1776

Answers (2)

Selman Gen&#231;
Selman Gen&#231;

Reputation: 101742

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.

Upvotes: 2

Ofir Winegarten
Ofir Winegarten

Reputation: 9365

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;

Upvotes: 1

Related Questions