How to unbox from object to type it contains, not knowing that type at compile time?

◇◆丶佛笑我妖孽 提交于 2019-11-30 10:53:21

If you don't know the type at compile time, then you can't unbox because you have nowhere to put it - all you can do is store it in an object, which is: boxed.

The same also applies to reference-types like string: you can't cast it to the right type if you don't know the type at compile time: you have nowhere to put it.

You can special-case a few types, for example:

if(obj is int) {
    int i = (int)obj;
    ...
} ...

Another trick that is sometimes (not often) helpful is to switch into generics; then instead of talking in terms of object you are talking in terms of T. This has... limited use though. The easiest way to do that is via dynamic, for example:

dynamic obj = ...
Foo(obj);
...
Foo<T>(T val) { ... code with T ... }

you can also add special cases to that appreach:

Foo(string val) { ... code with string ...}
Foo(int val) { ... code with int ...}

However, frankly I suggest it may be better to look hard at what you are trying to do.

Now lets suppose, that real boxing occur:

int v = 5;

object o = v; //boxed 

Type type = o.GetType(); //will return typeof(int)

int convertedBack = (int)Convert.ChangeType(o, type);

Console.WriteLine (convertedBack); //prints 5

Remark, if you substitute:

object convertedBack = Convert.ChangeType(o, type);

Console.WriteLine (convertedBack); //it still prints 5
Console.WriteLine (o); //it even print 5 here

The reason is that underlying object is still int. I've just used this example to show you, that boxing is irrelevant here. You need to rely on some abstraction in your operations and if you want to cast to int dynamically, what reference type do you wan to use.

In such cases I'm going to use the strategy pattern by using a Dictionary<Type, Action<object>>:

internal class Program
{
    private static void Main(string[] args)
    {
        var something = new Something();

        something.ComputeValue(13);
        something.ComputeValue(DateTime.Now);
        something.ComputeValue(DayOfWeek.Monday);

        Console.ReadKey();
    }
}

internal class Something
{
    private static Dictionary<Type, Action<object>> _Strategies;

    static Something()
    {
        // Prepare all available strategies.
        _Strategies = new Dictionary<Type, Action<object>>();
        _Strategies.Add(typeof(int), ComputeInteger);
        _Strategies.Add(typeof(DateTime), ComputeDateTime);
    }

    public void ComputeValue(object value)
    {
        Action<object> action;

        // Check if we have a matching strategy.
        if (!_Strategies.TryGetValue(value.GetType(), out action))
        {
            // If not, log error, throw exception, whatever.
            action = LogUnknownType;
        }

        // Perform the matching strategy on the given value.
        action(value);
    }

    private static void ComputeDateTime(object source)
    {
        // We get an object, but we are sure that it will always be an DateTime.
        var value = (DateTime)source;
        Console.WriteLine("We've got an date time: " + value);
    }

    private static void ComputeInteger(object source)
    {
        // We get an object, but we are sure that it will always be an int.
        var value = (int)source;
        Console.WriteLine("We've got an integer: " + value);
    }

    private static void LogUnknownType(object source)
    {
        // What should we do with the drunken sailor?
        var unknownType = source.GetType();
        Console.WriteLine("Don't know how to handle " + unknownType.FullName);
    }
}

Here's an example of exactly why you would do this:

class MyClass
{
   public int Id {get;set;}
   public string Name {get;set;}
   public decimal Val {get;set;}
}
int i = 0;
var myClassImp = new MyClass();
foreach (var val in new [object]{"10", "My name", "100.21"} // Could be read from some data source, such as an excel spreadsheet
{
   var prop = typeof(MyClass).GetProperties().ElementAt(i++);
   // !!!!!! THROWS EXCEPTION  !!!!!!!
   prop.SetValue(myClassImp, System.Convert.ChangeType(val, prop.PropertyType), null);
}

The reason for this is because the value is a boxed object... at runtime you do not know the type, so you would have to unbox to the prop.PropertyType

A pragmatic solution; try and use a TypeConverter directly, and if that fails, convert to string and back again:-

private static T GetValueOfType<T>(this ManagementBaseObject MBO, String FieldName) {
    T lResult;

    try {
        Object lObj = MBO[FieldName];

        var lSrcType = lObj.GetType();
        var lDestType = typeof(T);

        if (lDestType.IsValueType && lDestType.IsAssignableFrom(lSrcType)) {
            lResult = (T)lObj;
            return lResult;
        }

        var lDestTC = TypeDescriptor.GetConverter(typeof(T));
        if (lDestTC.CanConvertFrom(lSrcType)) {
            lResult = (T)lDestTC.ConvertFrom(lObj);
        } else {
            var lSrcTC = TypeDescriptor.GetConverter(lSrcType);
            String lTmp = lSrcTC.ConvertToInvariantString(lObj);
            lResult = (T)lDestTC.ConvertFromInvariantString(lTmp);
        }
    } catch {
        lResult = default(T);
    }
    return lResult;
}

Use expressions:

var y = DynamicCast(obj, desiredType);

static object DynamicCast(object source, Type type)
{
    var parameter = Expression.Parameter(typeof(object), "input");

    var cast = Expression.TypeAs(Expression.Convert(parameter, type), typeof(object));

    var lambda = Expression.Lambda<Func<object, object>>(cast, parameter);

    var func = lambda.Compile();

    return func(source);
}
public static string GetType(object data)
{
    Type type = data.GetType();
    return Convert.ChangeType(data, type).GetType().Name;
}

Hi,this method receives object data and returns string type name of object. Hope this is what you need.

you can try using the dynamic runtime

    [Test]
    public void Test_UnboxingAtRuntime()
    {
        object boxed = "Hello";

        //this line is commented out as it does not compile
        // OverloadedMethod(boxed);

        var result = CallCorrectMethod(boxed);
        Assert.That(result, Is.EqualTo("string"));

        boxed = 1;
        result = CallCorrectMethod(boxed);
        Assert.That(result, Is.EqualTo("int"));
    }

    public string CallCorrectMethod(dynamic t)
    {
        return OverloadedMethod(t);
    }

    public string OverloadedMethod(string s)
    {
        return "string";
    }

    public string OverloadedMethod(int s)
    {
        return "int";
    }
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!