使用Type变量强制转换变量

在c#中,我可以将类型为object的变量强制转换为类型为T的变量,其中T定义在Type变量中吗?

358629 次浏览

为了简单起见,将装箱和解装箱放在一边,沿着继承层次结构进行强制转换时不涉及特定的运行时操作。这主要是编译时的事情。本质上,强制转换告诉编译器将变量的值视为另一种类型。

试镜之后你能做什么?你不知道它的类型,所以你不能对它调用任何方法。你不会有什么特别的事情可以做。具体来说,只有当你在编译时知道可能的类型,手动转换它并使用if语句分别处理每种情况时,它才有用:

if (type == typeof(int)) {
int x = (int)obj;
DoSomethingWithInt(x);
} else if (type == typeof(string)) {
string s = (string)obj;
DoSomethingWithString(s);
} // ...

你怎么能这么做?您需要一个类型为T的变量或字段,以便在转换后存储对象,但是如果您只在运行时知道T,您如何拥有这样的变量或字段呢?所以,这是不可能的。

Type type = GetSomeType();
Object @object = GetSomeObject();


??? xyz = @object.CastTo(type); // How would you declare the variable?


xyz.??? // What methods, properties, or fields are valid here?

下面是一个类型转换和转换的例子:

using System;


public T CastObject<T>(object input) {
return (T) input;
}


public T ConvertObject<T>(object input) {
return (T) Convert.ChangeType(input, typeof(T));
}

编辑:

评论里有人说这个答案没有回答问题。但是(T) Convert.ChangeType(input, typeof(T))行提供了解决方案。Convert.ChangeType方法尝试将任何对象转换为作为第二个参数提供的类型。

例如:

Type intType = typeof(Int32);
object value1 = 1000.1;


// Variable value2 is now an int with a value of 1000, the compiler
// knows the exact type, it is safe to use and you will have autocomplete
int value2 = Convert.ChangeType(value1, intType);


// Variable value3 is now an int with a value of 1000, the compiler
// doesn't know the exact type so it will allow you to call any
// property or method on it, but will crash if it doesn't exist
dynamic value3 = Convert.ChangeType(value1, intType);

我用泛型写了答案,因为我认为当你想要在不处理实际类型的情况下将a something强制转换为a something else时,这很可能是代码气味的标志。在99.9%的情况下,不需要使用合适的界面。当涉及到反射时,可能有一些边缘情况是有意义的,但我建议避免这些情况。

编辑2:

一些额外的建议:

  • 尽量保持代码的类型安全。如果编译器不知道类型,那么它就不能检查你的代码是否正确,像自动完成这样的东西就不能工作。简单地说:如果不能在编译时预测类型,那么编译器如何能够预测?
  • 如果正在使用的类实现一个公共接口,则可以将值强制转换为该接口。否则,考虑创建自己的接口,并让类实现该接口。
  • 如果您正在使用动态导入的外部库,那么也要检查公共接口。否则,考虑创建实现接口的小型包装器类。
  • 如果要对对象进行调用,但不关心类型,则将值存储在objectdynamic变量中。
  • 泛型是创建可重用代码的好方法,可以应用于许多不同的类型,而不必知道具体涉及的类型。
  • 如果你被卡住了,那么考虑一种不同的方法或代码重构。你的代码真的必须是动态的吗?它必须考虑到任何类型吗?
public bool TryCast<T>(ref T t, object o)
{
if (
o == null
|| !typeof(T).IsAssignableFrom(o.GetType())
)
return false;
t = (T)o;
return true;
}

其他答案没有提到“动态”;类型。所以如果你想再加一个答案,你可以用“dynamic”。类型来存储生成的对象,而不必使用静态类型强制转换转换后的对象。

dynamic changedObj = Convert.ChangeType(obj, typeVar);
changedObj.Method();

记住,使用“dynamic”;编译器正在绕过静态类型检查,如果不小心,可能会引入运行时错误。

此外,还假定obj是typeVar类型的实例,或者可以转换为该类型。

甚至清洁:

    public static bool TryCast<T>(ref T t, object o)
{
if (!(o is T))
{
return false;
}


t = (T)o;
return true;
}

下面是我的方法,用于强制转换对象,但不强制转换为泛型类型变量,而是动态地转换为System.Type:

我在运行时使用System.Linq.Expressions创建了一个类型为Func<object, object>的lambda表达式,它将输入解框,执行所需的类型转换,然后给出装箱的结果。不仅需要对所有类型进行强制转换,而且还需要对被强制转换的类型进行强制转换(因为有开箱步骤)。创建这些表达式非常耗时,因为反射、编译和动态方法构建都是在底层完成的。幸运的是,表达式一旦创建,就可以重复调用,而且没有高开销,所以我缓存了每个表达式。

private static Func<object, object> MakeCastDelegate(Type from, Type to)
{
var p = Expression.Parameter(typeof(object)); //do not inline
return Expression.Lambda<Func<object, object>>(
Expression.Convert(Expression.ConvertChecked(Expression.Convert(p, from), to), typeof(object)),
p).Compile();
}


private static readonly Dictionary<Tuple<Type, Type>, Func<object, object>> CastCache
= new Dictionary<Tuple<Type, Type>, Func<object, object>>();


public static Func<object, object> GetCastDelegate(Type from, Type to)
{
lock (CastCache)
{
var key = new Tuple<Type, Type>(from, to);
Func<object, object> cast_delegate;
if (!CastCache.TryGetValue(key, out cast_delegate))
{
cast_delegate = MakeCastDelegate(from, to);
CastCache.Add(key, cast_delegate);
}
return cast_delegate;
}
}


public static object Cast(Type t, object o)
{
return GetCastDelegate(o.GetType(), t).Invoke(o);
}

请注意,这不是魔术。强制转换不像dynamic关键字那样在代码中发生,只转换对象的底层数据。在编译时,我们仍然需要费力地找出我们的对象可能是什么类型,这使得这个解决方案不切实际。我写这篇文章是为了调用由任意类型定义的转换操作符,但也许有人能找到更好的用例。

当谈到转换为Enum类型时:

private static Enum GetEnum(Type type, int value)
{
if (type.IsEnum)
if (Enum.IsDefined(type, value))
{
return (Enum)Enum.ToObject(type, value);
}


return null;
}

你可以这样称呼它:

var enumValue = GetEnum(typeof(YourEnum), foo);

这对我来说是必要的情况下,获得几个枚举类型的描述属性值的int值:

public enum YourEnum
{
[Description("Desc1")]
Val1,
[Description("Desc2")]
Val2,
Val3,
}


public static string GetDescriptionFromEnum(Enum value, bool inherit)
{
Type type = value.GetType();


System.Reflection.MemberInfo[] memInfo = type.GetMember(value.ToString());


if (memInfo.Length > 0)
{
object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), inherit);
if (attrs.Length > 0)
return ((DescriptionAttribute)attrs[0]).Description;
}


return value.ToString();
}

然后:

string description = GetDescriptionFromEnum(GetEnum(typeof(YourEnum), foo));
string description2 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum2), foo2));
string description3 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum3), foo3));

或者(更好的方法),这样的类型转换可以是这样的:

 private static T GetEnum<T>(int v) where T : struct, IConvertible
{
if (typeof(T).IsEnum)
if (Enum.IsDefined(typeof(T), v))
{
return (T)Enum.ToObject(typeof(T), v);
}


throw new ArgumentException(string.Format("{0} is not a valid value of {1}", v, typeof(T).Name));
}

如果需要在不知道目标类型的情况下在运行时强制转换对象,可以使用反射来生成动态转换器。

这是一个简化版本(没有缓存生成的方法):

    public static class Tool
{
public static object CastTo<T>(object value) where T : class
{
return value as T;
}


private static readonly MethodInfo CastToInfo = typeof (Tool).GetMethod("CastTo");


public static object DynamicCast(object source, Type targetType)
{
return CastToInfo.MakeGenericMethod(new[] { targetType }).Invoke(null, new[] { source });
}
}

然后你可以调用它:

    var r = Tool.DynamicCast(myinstance, typeof (MyClass));

当使用Zyphrax的答案时,没有找到任何东西来绕过“对象必须实现IConvertible”异常(实现接口除外)..我尝试了一些非常规的方法,但在我的情况下奏效了。

使用Newtonsoft。Json nuget包…

var castedObject = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(myObject), myType);

我永远不会理解为什么你需要50个声誉才能留下评论,但我不得不说@Curt的答案正是我想要的,希望是其他人。

在我的例子中,我有一个ActionFilterAttribute,我用来更新json补丁文档的值。我不知道补丁文档的T模型是什么,我必须序列化&将它反序列化为普通的JsonPatchDocument,修改它,然后因为我有类型,serialize &再次将其反序列化为该类型。

Type originalType = //someType that gets passed in to my constructor.


var objectAsString = JsonConvert.SerializeObject(myObjectWithAGenericType);
var plainPatchDocument = JsonConvert.DeserializeObject<JsonPatchDocument>(objectAsString);


var plainPatchDocumentAsString= JsonConvert.SerializeObject(plainPatchDocument);
var modifiedObjectWithGenericType = JsonConvert.DeserializeObject(plainPatchDocumentAsString, originalType );