【问题标题】:Explicitly cast from generic type to an object of its type parameter从泛型类型显式转换为其类型参数的对象
【发布时间】:2014-11-22 03:14:07
【问题描述】:

我想创建一个在概念上类似于Nullable<T>Validatable<T> 类。我有一个许多类型使用的IValidatable 接口,但是如果一个类型还没有实现这个接口,我想很容易地在它周围放置一个包装器,然后使用该包装器来存储验证错误。

我希望能够转换回被包装的对象,但是即使我有一个显式的运算符集(虽然实际上我更喜欢一个隐式的运算符),这个转换还是失败了。以下是定义:

    public interface IValidatable
    {
        bool IsValid { get; }
        void Validate();
        // (more validation-related methods here...)
    }

    public class Validatable<T> : IValidatable
        where T : class
    {
        public Validatable(T obj)
        {
            Object = obj;
        }

        public T Object { get; private set; }

        public bool IsValid { get; set; }

        public void Validate()
        {

        }

        public static implicit operator Validatable<T>(T other)
        {
            return new Validatable<T>(other);
        }

        public static explicit operator T(Validatable<T> other)
        {
            return other.Object;
        }

    }

    public static class Validatable
    {
        public static IValidatable AsValidatable<T>(T obj)
            where T: class
        {
            if (obj == null)
                return null;

            var already = obj as IValidatable;

            return already ?? new Validatable<T>(obj);
        }
    }

    public class Person // not IValidatable
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

但是如果我尝试:

    public void ConversionTest()
    {
        var person = new Person { FirstName = "Bob", LastName = "Smith" };

        var validatable = Validatable.AsValidatable(person);

        var cast = (Person)validatable; // FAILS here with InvalidCastException
    }

为什么会因 InvalidCastException 回退到 Person 失败?

【问题讨论】:

  • 我会投票支持保持开放,因为这个问题还涉及 Nullable 如何支持这一点。
  • @erikkallen:Nullable 不是语言规范/编译器中的特例吗?
  • @AndreyAkinshin 我查看了这个问题和许多其他处理泛型类型转换的问题,但我不相信他们中的任何一个都处理这个问题,因为IValidatable 接口的使用似乎是什么把我绊倒了。
  • 你不能在Validatable上暴露原始对象吗?类似validatable.GetUnderlying&lt;Person&gt;()

标签: c# generics casting


【解决方案1】:

Validatable.AsValidatable 返回 IValidatable,而不是 Validatable&lt;T&gt;,并且没有演员表。显然不能保证IValidatable 的实例也是Validatable&lt;T&gt; 的实例。

如果你忽略AsValidatable 并在另一个方向使用你的转换运算符,它确实有效:

    public static void ConversionTest()
    {
        var person = new Person { FirstName = "Bob", LastName = "Smith" };

        var validatable = (Validatable<Person>)person;           

        var cast = (Person)validatable; // FAILS here with InvalidCastException
    }

对于Nullable,他们作弊了——这是个特例。

【讨论】:

  • 这在技术上并没有错,但是 OP 已经定义了一个转换运算符来支持它,所以问题更多的是为什么该转换运算符不起作用。
  • 感谢您的解释。为了回答你的问题,我确实考虑了一些类似的事情,但这只是一个人为的例子来证明这个问题;在我的实际代码中,我不想区分实际实现 IValidatable 的类型和 Validatable&lt;T&gt; 包装的类型。
【解决方案2】:

如果你明确说明类型,问题就很明显了:

public void ConversionTest()
{
    Personperson = new Person { FirstName = "Bob", LastName = "Smith" };

    IValidatable validatable = Validatable.AsValidatable(person);

    Person cast = (Person)validatable; // FAILS here with InvalidCastException
}

由于validatable有接口类型,所以不能应用转换运算符(在Validatable&lt;T&gt;中声明)。

Nullable&lt;T&gt; 具有特殊的运行时支持来启用此方案。

【讨论】:

  • 好吧,我想“启用此方案的特殊运行时支持”在这里让我有些困惑,感谢您澄清这一点。顺便说一句,编译器的消息是“无法将'Validatable`1 [Person]'类型的对象转换为'Person'类型”。所以这里看起来真的很混乱;它应该是“无法将'IValidatable'类型的对象转换为'Person'类型”。无论如何,接受这个,因为我认为这是最明确的答案,谢谢。
  • 给你错误信息的不是编译器,而是运行时。我所说的特殊支持是,当运行时将 Nullable 装箱时,它将被装箱为 null 或基础值。
【解决方案3】:

这是因为 IValidatable 不能转换为 Person -- 只有 Validatable 可以。编译器将插入一个简单的强制转换并且无法调用您的运算符,因为它不知道具体类型。

作为您尝试做的替代方案,您可以走ComparerIComparable 的路线:实现Validator.Default 提供Validator,这将使用对象自己的IValidatable 实现,或构造另一个。这样,由于概念保持独立,因此无需将对象转换回。

【讨论】:

  • 嗯,Comparer/IComparable 解决方案听起来很有趣,但不幸的是,它可能涉及更改太多现有代码,在我的特定情况下不实用。不过感谢您的想法,我可能会进一步探索...
【解决方案4】:

从不为接口评估隐式和显式转换运算符。如果您想允许IValidatable 类型的引用可转换为某种类型的引用(希望是底层对象的引用),那么IValidatable 必须包含一个返回内容的属性[我会建议避免将名称 Object] 作为 System.Object 类型的引用,否则有一个通用方法 T GetContentAs&lt;T&gt;() 将尝试将内容作为特定类型返回。两者都不会提供公共类型安全,但在使用非泛型接口时这是可以预料的。

或者,您可以使用T 类型的只读Content 属性创建协变接口IValidatable&lt;out T&gt; 接口。无法使用转换运算符将内容从IValidatable&lt;Foo&gt; 提取到Foo,但可以简单地使用IValidatable&lt;Foo&gt;.Content 属性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多