0

Okay. I'll be brief. Why doesn't this work ?

//Find<T> returns the T object with a given name
//Not a true method, but that's simplier for the example.
Warrior conan = Find<Warrior> ("Conan");
//Debug.Log is just printing the results
Debug.Log (conan is Warrior); //True
Debug.Log (conan is Character); //True, Warrior extends Character
Character other = Find<Character> ("Conan"); // Null, Why ?

I guess that generic c# methods are quite differents in IL, and this is why that doesn't work. But it's quite annoying. Am I doing anything wrong ? Is there a way to bypass this ?

EDIT:

In fact, my method is a bit different. I'm using MVC and I want to find the view corresponding to a model.

public ElementView<E> Find<E> (E model) {

    ElementView<E>[] tab = gameBoardView.transform.GetComponentsInChildren<ElementView<E>> ();
    foreach (ElementView<E> el in tab)
        if (EqualityComparer<E>.Default.Equals(el.model, model))
            return el;
    return null;
}

And I use it like this :

ElementView<Warrior> w = Find<Warrior> (myCharacter); // good if my character is a warrior
ElementView<Character> c = Find<Character> (myCharacter); // null
F. Morival
  • 331
  • 1
  • 2
  • 9
  • 6
    adding the code of Find would help. – Regis Portalez Jun 29 '17 at 16:10
  • 4
    Please show the source of the `Find` method. – Sergey Kalinichenko Jun 29 '17 at 16:10
  • How to ask a good question: [ask] And how to provide good example code: [mcve] –  Jun 29 '17 at 16:12
  • 4
    my bet is that conan.getType() == "Warrior" and Find breaks on that. It would seem that Find<> needs to use the same `is` check your code is doing here – payo Jun 29 '17 at 16:12
  • To clarify what everybody else is saying: This has nothing to do with generics as such. It's an issue with the implementation of the particular method you're calling. If somebody wrote a `GreaterThanOrEqual(a, b)` method that returned `a == b`, that's not due to the nature of integers, it's due to the nature of whoever wrote the method. – 15ee8f99-57ff-4f92-890c-b56153 Jun 29 '17 at 16:17
  • That could be EqualityComparer which doesn't manage inheritance ? – F. Morival Jun 29 '17 at 16:27
  • Rufus L, I don't really use Find like that. I edited my post and there is an example of my uses at the bottom. – F. Morival Jun 29 '17 at 16:29
  • 2
    C# support variance only for interfaces and delegates, thus class `ElementView` is **not** subclass of `ElementView`. – user4003407 Jun 29 '17 at 16:29
  • How should I do then ? – F. Morival Jun 29 '17 at 16:33
  • 4
    Do you know what *covariance and contravariance* is? If not, learn that first. The key insight is: a bowl of apples cannot be used as a bowl of fruit: you can put a banana into a bowl of fruit but not into a bowl of apples. A bowl of fruit cannot be used as a bowl of apples because you can take a banana *out* of a bowl of fruit, but not out of a bowl of apples. Therefore bowls are *neither contravariant nor covariant*. In C# interfaces and delegates *might* be covariant or contravariant, but classes and structs *never* are. – Eric Lippert Jun 29 '17 at 16:36
  • How have you implemented equality for `Character`? – Lee Jun 29 '17 at 16:38
  • I didn't. They are equals if they have the same reference. – F. Morival Jun 29 '17 at 16:40
  • I think the problem isn't equality, but that ElementView is not subclass of ElementView. Is there any way to bypass that ? – F. Morival Jun 29 '17 at 16:42
  • 3
    @F.Morival: **Read my comment**. If you understood covariance and contravariance then you would know the answer to your question. Take this opportunity to learn about it, since it is the crux of your problem. – Eric Lippert Jun 29 '17 at 17:12
  • You should probably also read my series of articles about "wizards and warriors", as it is directly germane to your business logic here. – Eric Lippert Jun 29 '17 at 17:13
  • Have you tried `Character other = Find("Conan")` ? – David R Tribble Jun 29 '17 at 19:08
  • @DavidRTribble: his method is returning `null`. Assigning that `null` to a variable of a different type isn't going to change anything. – Peter Duniho Jun 29 '17 at 23:38

1 Answers1

2

As noted in the comments, you could probably answer this question yourself, if you would study variance in generic type parameters. Recommended reading includes:

In C#, why can't a List object be stored in a List variable
C# variance problem: Assigning List as List
Contravariance explained
Difference between Covariance & Contra-variance
Eric Lippert's Wizards and warriors series (this link is to part 1)

That said, in an effort to address your immediate concern and to possibly provide some practical, actionable information…

Your method breaks here:

ElementView<E>[] tab = gameBoardView.transform.GetComponentsInChildren<ElementView<E>> ();

Specifically, the type of the model is Warrior, which will have an instance of ElementView<Warrior> in the collection being searched by GetComponentsInChildren<T>(). When you call Find<Character>(), you call the generic method GetComponentsInChildren<Character>(), which will search for instances of ElementView<Character>.

Since ElementView<Warrior> is not an ElementView<Character> — that is, it doesn't inherit that type — it is excluded from the search. The search would return only instances of ElementView<Character>, which will not include the view for the model in question. (The returned collection may in fact be empty, if Character is simply the base class used by all of the real types…only if there are actual ElementView<Character> objects in the components collection would you even get any objects returned by the method.)

Whether you can do anything about this depends on the type ElementView<T> and whether you are able to introduce an interface type into your code to represent ElementView<T> objects. That is, it would be legal for GetComponentsInChildren<T>() to return objects of type ElementView<Warrior> when you ask for objects of type ElementView<Character> only if:

  1. You can change the code to return a collection of interface types instead of ElementView<T> types (e.g. IElementView<T>), and
  2. The interface's type parameter T can be declared as covariant; that is, with the out keyword, indicating that all members of the interface only ever return objects of type T, and not accept them as parameters.

If those things were true, then you could declare the appropriate interface, make sure that ElementView<T> implements that interface, and then objects of type ElementView<Warrior> could be cast to the interface type IElementView<Character>. Since the interface type parameter would be Character, and the interface implementation could only return objects of type Character, the fact that the object is actually returning an object of type Warrior would be fine. Warrior is (presumably) a Character, and so returning a Warrior satisfies the interface's declaration of returning Character values.

If you could meet those requirements, then the GetComponentsInChildren<T>() method could return an array of type IElementView<T>[], which could in fact contain your object of interest that is actually type ElementView<U> where U inherits T (i.e. GetComponentsInChildren<Character>() would return IElementView<Character>[], in which it would be valid to find an instance of IElementView<Warrior>).

Of course, you'd also need to change the code that uses these types, including GetComponentsInChildren<T>() (depending on its implementation). It's not a simple "throw the switch" kind of change to support variance in your generic types. But assuming your types are compatible with variant declarations, it can be a worthwhile effort.

I can't provide any specific advice on how exactly those changes would be made, or even if they are possible, since your question does not include a good Minimal, Complete, and Verifiable code example. If you want to make these changes, I recommend you study variance first, then make an effort on your own to change the code. If you still have trouble after that, post a new question, being sure to include a good MCVE showing clearly what you're trying to do, and explain precisely what it is you're still having trouble with.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136