0

I have a class called Structure<A, E, B, V, K> which inherits a generic method FireEdges<TA, TE, TB, TV, TK>(Func<TE, TK> transformer) through an interface.

I would like to run different code depending on whether the types passed to FireEdges match the instance types or not.

For example, if I have:

Structure<int, int, int, int, int> s = new ...

Then if I call s.FireEdges<int, int, int, int, int>(Func<int, int> transformer) it should run separate code than if I were to call s.FireEdges<A, B, C, D, E>(Func<B, E> transformer), for example.

To do this I've tried doing something like this:

public override void FireEdges<TA, TE, TB, TV, TK>(Func<TE, TK> transformer) {
    if (typeof(TE) == typeof(E) && ... ) //Check other types too
        foreach (E e in Edges)
            e.Target.Data = transformer(e);
    else 
        //Do something else since not all types match
}

I am getting this error :

Argument type 'E' is not assignable to parameter type 'TE'

even though I'm only trying to assign after checking they are indeed the same type.

Clearly I'm doing something very wrong, but I have no idea what it could be.

Thanks

meJustAndrew
  • 6,011
  • 8
  • 50
  • 76
Novicegrammer
  • 222
  • 1
  • 9
  • The compiler doesn't know that Edges in this case is actually an E so it will not all you to assign it. You will need to cast but can't do that directly see this https://stackoverflow.com/questions/6407039/explicitly-cast-generic-type-parameters-to-any-interface – rerun Sep 28 '18 at 21:30
  • 4
    wow , a generic class with 5 generic type args sure smells odd – pm100 Sep 28 '18 at 21:32
  • I want to add that what you are looking for is nativity supported by other languages like C++ though template specialization. – rerun Sep 28 '18 at 21:38

3 Answers3

3

I would like to run different code depending on whether the types passed to FireEdges match the instance types or not.

Never do that. Your code is not generic if you are doing that. Generics are intended to be generic, hence the name.

Clearly I'm doing something very wrong, but I have no idea what it could be.

Now you know. If you are doing a type test on a generic, you're almost certainly doing something wrong.

As other answers have noted, if you are hell bent on abusing generics in this way, you can introduce a cast to object and then a cast to the "outbound" type to get around the error. This may introduce a boxing penalty because historically the jitter has not optimized T -> boxed T -> T code paths. Maybe the jitter has been fixed to generate optimal code in this case, but I would check.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
0

The problem is that only you know that your parameter is of type TE but your compiler doesn't know it. You just need a cast:

e.Target.Data = transformer((TE)(object)e);
meJustAndrew
  • 6,011
  • 8
  • 50
  • 76
  • There is also no direct conversion from E to TE. You need something like `transformer((TE)(object)e);` – Mike Zboray Sep 28 '18 at 21:32
  • @mike z, sorry because I ask, but how could you know that? – meJustAndrew Sep 28 '18 at 22:06
  • He could know it by *compiling the code and getting an error out of the compiler*. C# does not allow you to directly cast from one type parameter to another if they are unconstrained; can you surmise why that restriction was implemented? – Eric Lippert Sep 28 '18 at 22:11
  • @Eric Lippert, I assume because the casts could go really wrong like between an integer and a concrete class with many properties, but still it's only an assumption. Well, in case these parameters would be constrained to the `class` type, would the cast to `object` still be required? – meJustAndrew Sep 28 '18 at 22:19
  • It's not just that the cast could fail -- that can always happen. The real concern is that you have a type `FooShape` and a type `BarShape` that has a user-defined conversion to `FooShape`. People expect if `T` is `FooShape` and `U` is `BarShape` and they cast a U to a T, that the user-defined conversion gets run. Now, suppose you are the compiler author: **how do you generate code that makes that happen that is still fast when the types are `object` and `string`**? The answer is: you can't. – Eric Lippert Sep 28 '18 at 22:22
  • 1
    Your choices are either (1) implement the feature so that every cast *starts the compiler again at runtime*, or (2) make it an error and make simple casts that you know to be safe *very fast*. C# took a third route: implement `dynamic` so that if you want to pay the price of (1) you can, and otherwise, we choose (2). – Eric Lippert Sep 28 '18 at 22:23
  • 1
    Wow, I never imagined that, thanks a lot for clarifying, @Eric Lippert! – meJustAndrew Sep 28 '18 at 22:27
  • I know it from experience, both from my own usage of C# and seeing this on StackOverflow. This question is quite common. At this point I don't need to go and try this, it's just a reflex. Looking at the question again, this is definitely suboptimal for the example use case because of boxing which I don't think can be avoided without a redesign of other parts of whatever OP is doing. – Mike Zboray Sep 28 '18 at 22:28
0

A generic that works only for a few type isn´t really generic, it´s just some specific type which hides himself in a generic pelt. So if your TE-argumen only works for instances of type E, you shouldn´t use generics. Instead use a normal method that may have the type, for instance ReadInt, ParseString or whatever,

Apart from this there´s nothing at compile-time that would constraint E to be of type TE, only a type-check at runtime. This there´s no way for the compiler to know how to make a TE from an E. You have to provide that information by using a cast. However as there´s no relationship between those types and in particular TE could even be a value-type you probably have to cast to object before:

var te = (TE)(object)e;
MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
  • The introduction of a boxing operation will possibly not be optimized out by the jit -- the last time I asked the jitter team about that, it did not, but that was ten years ago so things might have changed. In any event, I would expect this sort of solution to wreck the performance gains that are produced by using generics. – Eric Lippert Sep 28 '18 at 22:06
  • BTW, they're having a sale on generic pelts at Walmart this weekend. – Rufus L Sep 29 '18 at 00:41