Generic sharing with constraints

Suppose that we want to allow some methods to be called on an object of type T? Won’t the use of Object_t* prevent that, since we don’t have many methods on System.Object? Yes, this is correct. But we first need to express this idea to the C# compiler using generic constraints.

Take a look again in the script code for this post at the type named InterfaceConstrainedGenericType. This generic type uses a where clause to require that it type T be derived from a given interface, AnswerFinderInterface. This allows the ComputeAnswer method to be called. Recall from the previous blog post about method invocation that calls on interface methods require a lookup in a vtable structure. Since the FindTheAnswer method will make a direct function call on the constrained instance of type T, then the C++ code can still use the fully shared method implementation, with the type T represented by Object_t*.

If we start at the implementation of the HelloWorld_DemonstrateGenericSharing_m4 function, then jump to the definition of the InterfaceConstrainedGenericType_1__ctor_m11 function, we can see that this method is again a #define, mapping to the InterfaceConstrainedGenericType_1__ctor_m10456_gshared function. If we look just below that function for the implementation of the InterfaceConstrainedGenericType_1_FindTheAnswer_m10458_gshared function, we can see that indeed, this is the fully shared version of the function, taking an Object_t* argument. It calls the InterfaceFuncInvoker0::Invoke function to actually make the call to the managed ComputeAnswer method.

                extern "C" int32_t InterfaceConstrainedGenericType_1_FindTheAnswer_m10458_gshared (InterfaceConstrainedGenericType_1_t2160 * __this, Object_t * ___experiment, MethodInfo* method)
{
static bool s_Il2CppMethodIntialized;
if (!s_Il2CppMethodIntialized)
{
AnswerFinderInterface_t11_il2cpp_TypeInfo_var = il2cpp_codegen_class_from_type(&AnswerFinderInterface_t11_0_0_0);
s_Il2CppMethodIntialized = true;
}
{
int32_t L_0 = (int32_t)InterfaceFuncInvoker0 < int32_t > ::Invoke(0 /* System.Int32 HelloWorld/AnswerFinderInterface::ComputeAnswer() */, AnswerFinderInterface_t11_il2cpp_TypeInfo_var, (Object_t *)(*(&amp;___experiment)));
return L_0;
}
}

            

This all hangs together in the generated C++ code code because IL2CPP treats all managed interfaces like System.Object. This is a useful rule of thumb to help understand the code generated by il2cpp.exe in other cases as well.

Constraints with a base class

In addition to interface constraints, C# allows constraints to be a base class. IL2CPP does not treat all base classes like System.Object, so how does generic sharing work for base class constraints?

Since base classes are always reference types, IL2CPP uses the fully shared version of the generic methods for these types. Any code which needs to use a field or call a method on the constrained type is performs a cast in C++ to the proper type. Again, here we rely on the C# compiler to correctly enforce the generic constraint, and we lie to the C++ compiler about the type.