为啥我不能将泛型类型的一个实例转换为另一个实例?

Posted

技术标签:

【中文标题】为啥我不能将泛型类型的一个实例转换为另一个实例?【英文标题】:Why can't I cast one instantiation of a generic type to another?为什么我不能将泛型类型的一个实例转换为另一个实例? 【发布时间】:2014-09-05 11:41:25 【问题描述】:

我怎样才能实现一个结构,以便可以执行以下转换?

var a = new StatusedValue<double>(1, false);
var b = (StatusedValue<int>)a;

我的实现应该与Nullable&lt;T&gt; 类似,它工作正常。但是,此代码因System.InvalidCastException 而失败:

public struct StatusedValue<T>  where T : struct

    public StatusedValue(T value) : this(value, true)
    

    

    public StatusedValue(T value, bool isValid)
    
        this.value = value;
        this.isValid = isValid;
    

    private T value;
    private bool isValid;

    public static implicit operator StatusedValue<T>(T value)
    
        return new StatusedValue<T>(value);
    

    public static explicit operator T(StatusedValue<T> value)
    
        return value.value;
    

结果:

无法将“StatusedValue`1[System.Double]”类型的对象转换为类型 'StatusedValue`1[System.Int32]'。

【问题讨论】:

不是一个真正的答案,但也许这个(以及该系列的其余部分)对你来说可能很有趣:Nullable micro-optimization, part four 你确定这段代码会抛出 InvalidCastException 吗?我什至无法编译这段代码。 对不起,原代码有点不同,但重点是一样的 看看this question,看看 Jon Skeet 的回答和 Eric Lippert 的评论(比这更好吗?!?) 什么保持不变?如果原始代码不同,您需要发布模拟原始代码而不是不同的代码。如果您发布不同的代码,答案也会有所不同。 【参考方案1】:

这适用于Nullable&lt;T&gt; 类型,因为它们得到了编译器的特殊处理。这称为“提升的转换运算符”,您无法定义自己的。

来自the C# Specification 的第 6.4.2 节:

6.4.2 提升的转换运算符

给定一个用户定义的转换运算符,它将一个不可为空的值类型 S 转换为一个 不可为空的值类型 T,存在提升的转换运算符 从 S?到T?。这个提升的转换运算符执行 从 S 展开?到 S 后跟从 S 的用户定义转换 到 T 后跟从 T 到 T? 的换行,除了 null 值 年代?直接转换为空值 T?。提升的转换运算符 具有与其基础相同的隐式或显式分类 用户定义的转换运算符。术语“用户定义的转换” 适用于用户定义和提升转换的使用 运营商

【讨论】:

【参考方案2】:

如果您对调用方法感到满意,请尝试

public StatusedValue<U> CastValue<U>() where U : struct

    return new StatusedValue<U>((U)Convert.ChangeType(value, typeof(U)), isValid);

如果T 无法转换为U,这将很遗憾在运行时 而不是编译时抛出。

编辑:如下所述,如果您限制为IConvertible 以及/而不是struct,那么理论上每次转换都可以在编译时进行,您只会得到一个由于运行时值错误而导致运行时失败。

【讨论】:

不确定,但我认为如果 T 没有实现 IConvertible,即使 T 和 U 之间存在隐式或显式转换,这也会抛出。好消息是您可以将 T 限制为 IConvertible @InBetween 该死的,我认为你是对的。如果给定两种引用类型,我希望它至少会尝试运行时强制转换。听起来 OP 可能只想在基本类型之间进行。 嗯,它已经被限制在结构中,所以还不错。【参考方案3】:

Nullables是编译器专门处理的,不知道这里是不是这样。

您的运营商会允许这样做:

StatusedValue<int> b = (int)a;

这可能不是你想要的,因为IsValid 不是这样复制的。

你可以像这样实现一个扩展方法:

 public static StatusedValue<TTarget> Cast<TSource, TTarget>(this StatusedValue<TSource> source)
    where TTarget : struct
    where TSource : struct
  
    return new StatusedValue<TTarget>(
      (TTarget)Convert.ChangeType(
        source.Value, 
        typeof(TTarget)),
      source.IsValid);
  


  b = a.Cast<int>();

但是编译器无法检查类型是否兼容。 ChangeType 还返回一个对象,从而将您的值装箱。

【讨论】:

这不会编译,因为编译器不知道如何在TSourceTTarget 之间进行转换。 (也因为 ValueIsValid 不存在......)当它是他自己的类时,为什么要扩展方法? 我认为你是对的。之前需要强制转换(和框)对象。或者更好地进行转换。然而,这只是一个想法。【参考方案4】:

为什么的答案已经发布并标记为答案。

但是,您可以简化语法,使其更容易、更清晰地执行此操作,同时保持编译时类型安全。

首先,编写一个帮助类以避免必须指定冗余类型参数:

public static class StatusedValue

    public static StatusedValue<T> Create<T>(T value, bool isValid = true) where T: struct
    
        return new StatusedValue<T>(value, isValid);
    

接下来,您需要使用 Value 属性公开基础值(否则您无法从代码中强制转换它)。

最后你可以改变你原来的代码:

var a = new StatusedValue<double>(1, false);
var b = (StatusedValue<int>)a;

到这里:

var a = StatusedValue.Create(1.0, false);
var b = StatusedValue.Create((int)a.Value, false); 

您在a.Value 上进行简单演员表。

【讨论】:

他已经有一个构造函数来做你的Create 所做的事情。 @Gutblender 但是该构造函数要求您指定类型参数(即您必须将类型放入&lt;LikeThis&gt;),而静态帮助器类允许编译器从参数类型推断类型。这就是为什么我说“首先,编写一个帮助类以避免必须指定冗余类型参数” 是的。我确实注意到语法有点甜。但是问题变成了,OP 可以用一个持有ValueType 的非泛型类来代替吗?我已经制作了一个与此非常相似的结构,其目的是为了描述强制性和可选功能。它适用于此。【参考方案5】:

作为一种解决方法,您需要提供一种从一种基础类型转换为另一种的方法,因为编译器无法解决这个问题:

public StatusedValue<TResult> To<TResult>(Func<T, TResult> convertFunc)
where TResult : struct 
   return new StatusedValue<TResult>(convertFunc(value), isValid);

你可以这样做:

var a = new StatusedValue<double>(1, false);
var b = a.To(Convert.ToInt32);

通过一些反思,您可以构建Convert 方法的查找表,并根据类型参数查找正确的方法,然后您可以将转换函数默认为null,如果未提供,请尝试自动查找正确的转换。这将删除笨拙的Convert.ToInt32 部分,只需执行var b = a.To&lt;int&gt;();

正如 Rawling 指出的,Convert.ChangeType 可以使用。这将使我的方法看起来像:

public StatusedValue<T2> To<T2>(Func<T, T2> convertFunc = null)
where T2 : struct 
   return new StatusedValue<T2>(
      convertFunc == null
         ? (T2)Convert.ChangeType(value, typeof(T2))
         : convertFunc(value),
      isValid
   );

【讨论】:

With some reflection you could build a lookup table of the Convert methods 它叫Convert.ChangeType【参考方案6】:

如果你不需要强制转换,你可以添加这样的方法:

    public StatusedValue<int> ConvertToStatusedInt() 
        return new StatusedValue<int>(Convert.ToInt32(value), isValid);
    

如评论中所建议:

    public StatusedValue<Q> ConvertTo<Q>() where Q:struct 
        return new StatusedValue<Q>((Q)Convert.ChangeType(value, typeof(Q)), isValid);
    

【讨论】:

拥有强制转换能力确实不错,因为所有基本类型都可以用作泛型参数。

以上是关于为啥我不能将泛型类型的一个实例转换为另一个实例?的主要内容,如果未能解决你的问题,请参考以下文章

递归泛型类型的实例化速度越慢,嵌套越深。为啥?

泛型中的类型擦除

泛型之泛型方法

在没有泛型类约束的情况下,将泛型类型与其默认值进行比较,会产生编译时错误

第六节:Java泛型

泛型的相关