简体   繁体   中英

c# generic function type not adapting to overloads

I'm writing a generic function (templated function?) in unity c# and I encountered this problem. The function was ran templated with a type deriving from Component

T component = transform.gameObject.GetComponent<T>();
if(component != null)
{
    objects.Add(component);
}

I stepped through this and the value of component was "null" (which i expected) but then the program carried on and entered the if block...

In unity overloads the == / != operators to allow for null checks on their components (which are never actually null - so for example, component ??= foo should not work), but the overload of this function above should be fine when the type of T is a Component ?

I eventually managed to get it working with:

if(component as Component != null){...}

But my question is, why was this necessary? Does c# not check for operator overloads of types passed into generic functions? Surely the literal type passed is swapped in at runtime?

You're right, the actual type argument is swapped in at runtime.

But the operator calls are bound statically at compile time. Operators are not instance methods of types, there are static methods defined on types. So code like this:

Component a, b;

a == b

resolves to

Component a, b;

Component.op_Equality(a, b)

at compile time 1 .

When you write a generic method without any constraints the compiler treats T as the most general type it can be: object . This allows you to use any methods that are defined on object , since any type substituted for T will have those methods. But it can't assume anything else, since it is entirely possible the user is going to actually supply object as the argument for T . So code like this:

Foo<T>(T a) {
  if (a == null) {
    // ... 
  }
}

has to resolve to a simple null reference check, since you can compare any object with null just by checking whether the reference equals 0 or not. It cannot resolve to

Foo<T>(T a) {
  if (Component.op_Equality(a, null)) {
    // ...
  }
}

since at runtime you might supply object as T and pass something that is not assignable to Component to Foo . Generic methods are compiled only once for all reference types and then reused with metadata about the actual type arguments .

But you can help the compiler by saying

Foo<T>(T a) where T : Component {
  ...
}

Now you constrained possible values of T to subtypes of Component , so the compiler happily emits a call to the Component 's equality check. If you add this constraint to your method it will solve your problem.

Judging by your use of the word "template" you might have a C++ background: the important thing to note about generics is that they are very different from C++ templates, in particular, as mentioned, they are compiled once (for reference types) so the code inside them has to work with any possible value of T . It does not recompile the code inside a generic method every time you call it (like template instantiations in C++).

=====

1 Note that the name op_Equality is not visible to the user, you can't call the method verbatim from C# code, you need to use the == operator.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM