我想创建一个Validatable<T>
类,其概念与Nullable<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失败?
答案 0 :(得分:3)
Validatable.AsValidatable
返回IValidatable
,而不是Validatable<T>
,而且没有演员。显然,我无法保证IValidatable
的实例也是Validatable<T>
的实例。
如果您忽略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
他们作弊 - 这是一个特例。
答案 1 :(得分: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<T>
中声明)。
Nullable<T>
具有特殊的运行时支持以启用此方案。
答案 2 :(得分:1)
这是因为IValidatable
无法转换为Person
- 只有Validatable
。编译器将插入一个简单的强制转换,但不能调用你的运算符,因为它不知道具体的类型。
作为您尝试做的替代方案,您可以选择Comparer
和IComparable
的路线:实施Validator.Default
,其中Validator
将会IValidatable
使用对象自己的{{1}}实现,或构造另一个。这样,就不需要回放对象,因为概念保持独立。
答案 3 :(得分:1)
永远不会为接口评估隐式和显式转换运算符。如果要允许类型IValidatable
的引用可转换为某种类型的引用(希望是基础对象的引用),那么IValidatable
必须包含一个返回内容[我建议避免使用名称Object
]作为System.Object
类型的引用,或者使用通用方法T GetContentAs<T>()
来尝试将内容作为特定类型返回。两者都不会提供公共类型安全性,但在使用非通用接口时也是如此。
或者,您可以使用类型为IValidatable<out T>
的只读Content
属性创建协变接口T
接口。一个人无法使用强制转换操作符从IValidatable<Foo>
提取内容到Foo
,但可以使用IValidatable<Foo>.Content
属性。