使用类型变量转换变量


280

在C#中,我可以将类型为object的变量转换为类型T的变量,其中T是在类型变量中定义的吗?


12
这并不是严格意义上的话题,但是您对“ cast”的含义似乎已经很模糊了,这可能是个好主意,准确了解强制转换运算符的目的和语义是什么。这是一个好的开始:blogs.msdn.com/ericlippert/archive/2009/03/19/…–
埃里克·利珀特

2
我以为我想出了点什么。如果有Type变量,则可以使用反射创建该类型的实例。然后,您可以使用泛型方法通过从该类型的参数推断出所需的类型来返回该类型。不幸的是,任何创建类型实例的反射方法都将具有的返回类型object,因此您的通用CastByExample方法也将使用object。因此,实际上没有办法做到这一点,即使有,您将如何处理新广播的对象?您无法使用其方法或其他任何方法,因为您不知道其类型。
凯尔·德莱尼

@KyleDelaney谢谢,我完全同意!正如我试图在答案中解释的那样,在某些时候不定义您实际使用的Type,将某事物转换为另一事物并不是真正有用。类型的重点是编译器时间类型检查。如果只需要对该对象进行调用,则可以使用objectdynamic。如果要动态加载外部模块,则可以让这些类共享一个公共接口,并将对象强制转换为该接口。如果您不控制第三方代码,请创建小型包装器并在其上实现接口。
Zyphrax

Answers:


203

这是强制转换和转换的示例:

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方法尝试将任何Object转换为作为第二个参数提供的Type。

例如:

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变量中。
  • 泛型可能是创建适用于许多不同类型的可重用代码的好方法,而无需知道所涉及的确切类型。
  • 如果您陷入困境,请考虑使用其他方法或代码重构。您的代码真的必须具有动态性吗?是否必须考虑到任何类型?

145
我不知道这对OP有何帮助。她有一个类型变量,并非T如此。
nawfal

12
@nawfal,基本上该行Convert.ChangeType(input, typeof(T));提供了解决方案。您可以轻松地typeof(T)用现有的类型变量替换。更好的解决方案(如果可能)将是一起防止动态类型。
Zyphrax

59
@Zyphrax,不,它仍然需要强制转换,但T不可用。
nawfal

4
我知道结果对象确实是类型,T但您仍然只能得到object一个引用。嗯,我发现这个问题很有趣,前提是OP仅具有Type变量而没有其他信息。好像方法签名是Convert(object source, Type destination):)尽管如此,我还是很了解
nawfal

10
如何解决这个问题?我遇到了同样的问题,并且没有通用的<T>。我只有一个类型变量。
Nuri Tasdemir '16

114

其他答案没有提到“动态”类型。因此,要添加一个答案,您可以使用“动态”类型存储生成的对象,而不必强制转换为静态类型的转换对象。

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

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


19
这是正确的答案。没有动态关键字,typeof(changedObj)是“对象”。使用dynamic关键字,它可以完美工作,并且typeof(changedObject)可以正确反映与typeVar相同的类型。另外,您不需要(T)进行类型转换,如果您不知道类型,则无法进行转换。
rushinge

5
使用此解决方案时,出现了“对象必须实现IConvertible”异常。有什么帮助吗?
Nuri Tasdemir '16

@NuriTasdemir很难说,但是我相信,如果没有IConvertible,您将无法进行转换。转换涉及哪些类型?
maulik13 2013年

尽管这可行,但使用动态功能会降低性能。我建议不要使用它们,除非您正在使用其他运行时(这是动态设计的目的)。
博洛

19

这是我的方法,用于将对象强制转换为通用类型变量,而不是System.Type动态类型:

我在运行时使用System.Linq.Expressions类型为的Lambda表达式Func<object, object>,将其输入拆箱,执行所需的类型转换,然后将结果装箱。一个新的不仅需要转换为所有类型,而且还需要转换为所有类型(由于取消装箱步骤)。创建这些表达式非常耗时,因为需要在后台进行反射,编译和动态方法构建。幸运的是,一旦创建了这些表达式,就可以重复调用这些表达式,而不会产生很高的开销,因此我缓存了每个表达式。

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关键字那样进行强制转换,仅转换对象的基础数据。在编译时,我们仍然需要努力找出确切的对象类型,从而使该解决方案不切实际。我将其写为一种调用由任意类型定义的转换运算符的技巧,但也许有人可以找到更好的用例。


2
需要using System.Linq.Expressions;
Aaron D

4
对我来说,这与Zyphrax的答案同样存在问题。我无法在返回的对象上调用方法,因为它仍然是“对象”类型。无论我使用他的方法(以下为“ a”)还是您的方法(以下为“ b”),我在(t)Type t = typeof(MyGeneric<>).MakeGenericType(obj.OutputType); var a = (t)Convert.ChangeType(obj, t); var b = (t)Caster.Cast(t, obj);
强制转换中都会

@muusbolla Zyphrax的原始答案使用泛型和类型变量,而不是Type。如果您拥有的只是Type对象,则无法使用常规转换语法进行转换。如果希望在编译时而不是在运行时将对象用作某种T类型,则需要使用类型变量或仅使用实际的类型名称来强制转换它。您可以使用Zaphrax的答案做前者。
阿什利

8

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

演出后您能做什么?您不知道类型,因此您将无法对其调用任何方法。您不会有任何特别的事情可以做。具体来说,只有当您在编译时知道可能的类型,手动进行强制转换并使用if语句分别处理每种情况时,它才有用:

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

1
您能否就我的问题解释清楚一点?
theringostarrs,2009年

我想解释的是,那之后您将能够做什么?您不能做太多,因为C#编译器需要静态输入才能对对象执行有用的操作
Mehrdad Afshari

你是对的。我知道两个变量的预期类型,这些变量作为“对象”类型发送给方法。我想转换为存储在变量中的预期类型,并将其添加到集合中。更容易按类型分支并尝试正常的强制转换并捕获错误。
theringostarrs,2009年

4
您的回答很好,但是请谨慎选择,我注意到强制转换永远不会影响变量。将变量强制转换为其他类型的变量永远是不合法的。变量类型在C#中是不变的。您只能投的存储在变量为另一种类型。
埃里克·利珀特

C#4.0对动态类型的介绍是否会更改此答案?
Daniel T.

6

你怎么能做到这一点?您需要类型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?

3
如果使用通用类定义了返回值为T类型的方法,则可能需要这样做。例如,将字符串解析为T的实例并将其返回。
奥利弗·弗里德里希

7
幸运的是,这不是正确的答案。请参阅maulik13的答案。
rushinge

3
您以天堂的名义在哪里找到CastTo方法Object
ProfK

3

当涉及到枚举类型时:

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值获取多个枚举类型的Description属性值的情况,这对我来说至关重要:

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));
    }

1

在使用Zyphrax的答案没有找到解决方法后,“对象必须实现IConvertible”异常(实现接口除外)。我尝试了一些非常规的方法,并针对我的情况进行了工作。

使用Newtonsoft.Json nuget包...

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

1

有害,问题在于您没有T。

您只有一个Type变量。

提示MS,如果您可以做类似的事情

TryCast<typeof(MyClass)>

如果能解决我们所有的问题。


0

我永远不会理解为什么您需要高达50的声誉才能发表评论,但我只想说@Curt答案正是我一直在寻找的东西,并希望其他人也可以。

在我的示例中,我有一个ActionFilterAttribute用来更新json补丁文档的值。我不是补丁文档的T模型,我不得不将其序列化和反序列化为一个普通的JsonPatchDocument,对其进行修改,然后因为我有了类型,所以再次对其进行序列化和反序列化为该类型。

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 );

-1
public bool TryCast<T>(ref T t, object o)
{
    if (
        o == null
        || !typeof(T).IsAssignableFrom(o.GetType())
        )
        return false;
    t = (T)o;
    return true;
}

2
您能否指出该答案与其他答案有何不同以及该解决方案合适的地方?
克劳斯·古特(KlausGütter),

-2

更干净:

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

        t = (T)o;
        return true;
    }

-2

如果您需要在运行时强制转换对象而不知道目标类型,则可以使用反射来创建动态转换器。

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

    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));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.