为啥我不能将泛型类型的一个实例转换为另一个实例?
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<T>
类似,它工作正常。但是,此代码因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<T>
类型,因为它们得到了编译器的特殊处理。这称为“提升的转换运算符”,您无法定义自己的。
来自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 还返回一个对象,从而将您的值装箱。
【讨论】:
这不会编译,因为编译器不知道如何在TSource
和TTarget
之间进行转换。 (也因为 Value
和 IsValid
不存在......)当它是他自己的类时,为什么要扩展方法?
我认为你是对的。之前需要强制转换(和框)对象。或者更好地进行转换。然而,这只是一个想法。【参考方案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 但是该构造函数要求您指定类型参数(即您必须将类型放入<LikeThis>
),而静态帮助器类允许编译器从参数类型推断类型。这就是为什么我说“首先,编写一个帮助类以避免必须指定冗余类型参数”
是的。我确实注意到语法有点甜。但是问题变成了,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<int>();
正如 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);
【讨论】:
拥有强制转换能力确实不错,因为所有基本类型都可以用作泛型参数。以上是关于为啥我不能将泛型类型的一个实例转换为另一个实例?的主要内容,如果未能解决你的问题,请参考以下文章