Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cast a generic type without knowing the type

Tags:

c#

generics

I am trying to create a generic object in runtime. So far I have been able to create it but I can't figure out how to cast it. What I have is the enum object and I want to generate the EnumMapper that converts the enum value to custom string to map to a legacy database.

Type enumType = myEnum.GetType();
Type enumMapperType = typeof(EnumMapper<>)
                      .GetGenericTypeDefinition().MakeGenericType(enumType);
var mapper = Activator.CreateInstance(enumMapperType); // OK
EnumMapper<> mapper = (EnumMapper<>) Activator.CreateInstance(enumMapperType); // Error

When I inspect the object in the debugger it is create as I expect, but how can I cast it so I can use it?

The class:

public class EnumMapper<T> : IEnumMapper<T>

The interface:

public interface IEnumMapper<T>
{
    T EnumValue(string value);

    bool HasEnumValue(string stringValue);

    bool HasStringValue(T enumValue);

    string StringValue(T enumValue);
}

Error   2   ; expected  \EnumMapperTest.cs  36
Error   4   ; expected  \EnumMapperTest.cs  36
Error   1   Invalid expression term '>' \EnumMapperTest.cs  36
Error   3   Invalid expression term '>' \EnumMapperTest.cs  36
Error   34  Only assignment, call, increment, decrement, and new object expressions can be used as a statement  \EnumMapperTest.cs  36
Error   36  The name 'mapper' does not exist in the current context \EnumMapperTest.cs  36
Error   35  Using the generic type 'EnumMapper<T>' requires 1 type arguments    \EnumMapperTest.cs  36
Error   37  Using the generic type 'EnumMapper<T>' requires 1 type arguments    \EnumMapperTest.cs  36
like image 478
uncletall Avatar asked Nov 02 '22 10:11

uncletall


1 Answers

As far as I know, the exact thing you need is not possible in C#. Basically, you need for a method to have different types or the returned variable, based on the value of a regular, non-generic, parameter, i.e. if the parameter is typeof(Enum1), than the result variable is EnumMapper<Enum1> and if the parameter is typeof(Enum2), than the result variable is EnumMapper<Enum2>.

You can do this with generic parameters, however, since generics is all about compile-time information, and you only have the value at runtime, they cannot be used in this case.

What you can do (and what I have done) is to use dynamic code to work around this problem, being careful to go into statically-typed land as soon as possible (dynamic is really contagious, some say like a smile, some say like a virus):

public dynamic GetMapperObject(Type enumType)
{
  Type enumMapperType = typeof(EnumMapper<>)
                           .GetGenericTypeDefinition()
                           .MakeGenericType(enumType);
  var mapper = Activator.CreateInstance(enumMapperType);
  return mapper;
}

with calling code like:

var mapper = GetMapperObject(enumType);
//dynamic call, bind the resut to a statically typed variable
bool result = mapper.HasEnumValue("SomeStringValue") 

(old answer, just adds another level of indirection to the problem :) )

You could wrap all that in a generic method, something like this:

public EnumMapper<T> GetMapperObject<T>()
{
  Type enumType = typeof(T);
  Type enumMapperType = typeof(EnumMapper<>)
                           .GetGenericTypeDefinition()
                           .MakeGenericType(enumType);
  var mapper = Activator.CreateInstance(enumMapperType);
  return mapper as EnumMapper<T>;
}

and invoke it with

var mapper = GetMapperObject<EnumMapperTestEnum>();

However, if you only have a single value for the enum, you can use type inference, something like:

//everything is the same, just different signature
public EnumMapper<T> GetMapperByExample<T>(T item)
{
  Type enumType = typeof(T);
  Type enumMapperType = typeof(EnumMapper<>)
                           .GetGenericTypeDefinition()
                           .MakeGenericType(enumType);
  var mapper = Activator.CreateInstance(enumMapperType); // OK
  return mapper as EnumMapper<T>;
}

and call it with type inference

var mapper = GetMapperByExample(EnumMapperTestEnum.SomeValue); 
like image 57
SWeko Avatar answered Nov 08 '22 06:11

SWeko