简体   繁体   中英

How can I cast a variable with the `object` type to class with generic type in c#?

I have a project written in C#. I am trying to cast an object to a class that accepts enum object as a generic argument.

I have the following class

public class GenericEnumViewModel<TEnum> where TEnum : struct
{
    [Required]
    public TEnum? Value { get; set; }
    public IEnumerable<SelectListItem> Options { get; set; }
}

Here is an example of one of my enum objects

public enum TestEnum
{
    First,
    Second,
    Third,
    Fourth
}

Finally, I have a variable with an object type something like this

object obj = new GenericEnumViewModel<TestEnum>(); // Notice at this point I don't know that the generic type is `TestEnum` it could be any Enum.

How can I check if the variable obj implements GenericEnumViewModel<Enum> ? Also how can I make a new variable called castedObj as GenericEnumViewModel<Enum>

I am trying to access castedObj.Value and castedObj.Options .

I want to be able to do something like this

var castedObj = obj as GenericEnumViewModel<Enum>;

if(castedObj != null)
{
      // do something with castedObj.Value and castedObj.Options.
}

How can I check if the variable obj implements GenericEnumViewModel ?

You can do this using reflection:

object obj = new GenericEnumViewModel<TestEnum>();

var objType = obj?.GetType();
var enumType =
    objType != null && objType.IsGenericType && objType.GetGenericTypeDefinition() == typeof(GenericEnumViewModel<>) ?
    objType.GetGenericArguments()[0] :
    throw new InvalidOperationException($"Object is not a closed type of {typeof(GenericEnumViewModel<>).FullName}");

Also how can I make a new variable called castedObj as GenericEnumViewModel

This is impossible for multiple reasons. The following line won't compile:

// this code is invalid!
GenericEnumViewModel<Enum> model = new GenericEnumViewModel<TestEnum>();

First of all, covariance is only allowed for interfaces, arrays and delegates in C#.

Furthermore, even if covariance were allowed for classes, System.Enum is a reference type while enums are value types. Assigning an enum value to a System.Enum variable involves boxing . This in itself would make the assignment above impossible.

However, you can workaround this by the help of a non-generic interface (or abstract base class) like this:

public interface IGenericEnumViewModel
{
    Enum Value { get; }
    GenericEnumViewModel<SelectListItem> Options { get; }
}

public class GenericEnumViewModel<TEnum> : IGenericEnumViewModel where TEnum : struct
{
    [Required]
    public TEnum? Value { get; set; }
    public GenericEnumViewModel<SelectListItem> Options { get; set; }

    Enum IGenericEnumViewModel.Value => Value.HasValue ? Value.Value : (Enum)null;
}

// ...

if (obj is IGenericEnumViewModel castedObj)
{
    // do something with castedObj.Value and castedObj.Options.
    Enum value = castedObj.Value;
    // ...
}

Bonus

As of C# 7.3 you can use Enum in generic type constraints !

public class GenericEnumViewModel<TEnum> : IGenericEnumViewModel where TEnum : struct, Enum
{
    // ...
}

You cannot do it. GenericEnumViewModel<TEnum> it's open constructed type because it doesn't specify exactly the type of generic argument. The CLR does not allow any instance of an open type to be constructed and you can't declare a variable using open type outside the generic type code itself. You can do like this inside the GenericEnumViewModel<TEnum> class:

 GenericEnumViewModel<TEnum> viewModel;

But you can't do it outside the GenericEnumViewModel<TEnum> class. After all TEnum keyword doesn't ever exist outside the GenericEnumViewModel<TEnum> .

So, you can't do like this outside the GenericEnumViewModel<TEnum>

var castedObj = obj as GenericEnumViewModel<Enum>;

because it's the same thing as

GenericEnumViewModel<TEnum> viewModel = obj as GenericEnumViewModel<Enum>;

When you use var compiler should infer variable type and create variable with an inferred type. But it can't create variable with GenericEnumViewModel<TEnum> type, because it's open type and because TEnum doesn't exist in this context.

You can add new interface and implement it explicitly. Then You can use pattern matching which was introduced in C# 7.0.

public interface IGenericEnumViewModel
{
    object Value { get; set; }
    GenericEnumViewModel<SelectListItem> Options { get; set; }
}

public class GenericEnumViewModel<TEnum> : IGenericEnumViewModel where TEnum : struct
{
    public TEnum? Value { get; set; }
    public GenericEnumViewModel<SelectListItem> Options { get; set; }

        object IGenericEnumViewModel.Value {
               get {return Value;}
               set {Value = (TEnum?)value;}
}

And then:

if(obj is IGenericEnumViewModel c)
{
      //c is IGenericEnumViewModel 
}

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