为什么可以';t我将泛型类型的一个实例化强制转换为另一个实例化

本文关键字:实例化 一个 另一个 转换 泛型类型 为什么 | 更新日期: 2023-09-27 18:20:49

如何实现结构以便执行以下强制转换?

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]"的对象强制转换为类型'状态值`1[System.Int32]'.

为什么可以';t我将泛型类型的一个实例化强制转换为另一个实例化

这适用于Nullable<T>类型,因为它们受到编译器的特殊处理。这被称为";提升转换操作器";,你无法定义自己。

来自C#规范第6.4.2节:

6.4.2提升转换操作员

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

如果您很乐意调用一个方法,请尝试

public StatusedValue<U> CastValue<U>() where U : struct
{
    return new StatusedValue<U>((U)Convert.ChangeType(value, typeof(U)), isValid);
}

不幸的是,如果T不能转换为U,这将导致运行时而不是编译时。

编辑:如下所述,如果您约束到IConvertible和/而不是struct,那么理论上每次转换在编译时都是可能的,并且您只会因为运行时值不正确而导致运行时失败。

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还返回一个对象,从而装箱您的值。

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

但是,您可以简化语法,使其更容易、更清晰,同时保留编译时类型的安全性。

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

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上做一个简单的演员阵容。

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

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

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

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