0

I want to create a Validatable<T> class which is similar in concept to Nullable<T>. I have an IValidatable interface used by many types, but if a type doesn't already implement this interface, I want to make it easy to put a wrapper around it and then use that wrapper to store validations errors.

I want to be able to cast back to the wrapped object, but the cast is failing even though I have an explicit operator set (although actually I'd prefer an implicit operator). Here's the definitions:

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

But then if I try:

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

        var validatable = Validatable.AsValidatable(person);

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

Why is casting back to Person failing with an InvalidCastException?

Tobias J
  • 19,813
  • 8
  • 81
  • 66
  • possible duplicate of [Generic explicit cast failure C#](http://stackoverflow.com/questions/11934134/generic-explicit-cast-failure-c-sharp) – AndreyAkinshin Nov 21 '14 at 17:44
  • I'd vote for leaving it open since this question also involves how Nullable can support this. – erikkallen Nov 21 '14 at 17:47
  • @erikkallen: Is `Nullable` not a special case in the language spec/compiler though? – Jon Egerton Nov 21 '14 at 17:49
  • @AndreyAkinshin I viewed that question and many others dealing with casting generic types but I don't believe any of them deal with exactly this issue, as the use of the `IValidatable` interface seems to be what tripped me up here. – Tobias J Nov 21 '14 at 17:49
  • Can't you expose the original object on Validatable? Something like `validatable.GetUnderlying()` – PeteGO Nov 08 '17 at 23:01

4 Answers4

3

A Validatable.AsValidatable returns IValidatable, not Validatable<T>, and there's no cast for that. Obviously there's no guarantee that an instance of IValidatable is also an instance of Validatable<T>.

It does work if you do if you ignore AsValidatable and use your cast operator in the other direction though nicely:

    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
    }

For Nullable they cheated - its a special case.

Jon Egerton
  • 40,401
  • 11
  • 97
  • 129
  • This is not technically wrong, but the OP has defined a conversion operator to support it, so the question is more why that conversion operator does not work. – erikkallen Nov 21 '14 at 17:48
  • Thanks for the explanation. To answer your question, I did consider something along those lines, but this is just a contrived example to demonstrate the issue; in my actual code I don't want to have to distinguish between a type that actually implements `IValidatable` and one what is wrapped by `Validatable`. – Tobias J Nov 21 '14 at 17:55
2

If you spell out the types explicitly, the problem becomes obvious:

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

    IValidatable validatable = Validatable.AsValidatable(person);

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

Since validatable has the interface type, the conversion operator (declared in the Validatable<T>) cannot be applied.

Nullable<T> has special runtime support to enable this scenario.

erikkallen
  • 33,800
  • 13
  • 85
  • 120
  • Well I guess the "special runtime support to enable this scenario" is what was confusing me a bit here, thanks for clarifying that. BTW the compiler's message was "Unable to cast object of type 'Validatable`1[Person]' to type 'Person'". So that seems really confusing here; it should be "Unable to cast object of type 'IValidatable' to type 'Person'" instead. Anyway, accepting this since I think it's the clearest answer, thanks. – Tobias J Nov 21 '14 at 18:02
  • 1
    It is not the compiler that gives you the error message but the runtime. The special support I was talking about is that when the runtime boxes a Nullable, it will be boxed as either null or the underlying value. – erikkallen Nov 22 '14 at 12:23
1

It's because IValidatable is not convertible to Person -- only Validatable is. The compiler will insert a simple cast and cannot invoke your operator because it doesn't know the concrete type.

As an alternative to what you're trying to do, you could go the route of Comparer and IComparable: implement Validator.Default that gives a Validator which will either use the object's own IValidatable implementation, or constructs another. This way, there is no need for casting objects back because the concepts remain separate.

Jeroen Mostert
  • 27,176
  • 2
  • 52
  • 85
  • Hmmm, the `Comparer`/`IComparable` solution sounds interesting but unfortunately it would probably involve changing too much existing code to be practical in my particular case. Thanks for the idea though, I may explore that a bit further... – Tobias J Nov 21 '14 at 18:05
1

Implicit and explicit conversion operators are never evaluated for interfaces. If you want to allow a reference of type IValidatable to be convertible to a reference of some type (which is hopefully that of the underlying object), then either IValidatable will have to include a property that returns the content [I would recommend avoiding the name Object] as a reference of type System.Object, or else have a generic method T GetContentAs<T>() that will try to return the content as a particular type. Neither will provide public type safety, but that's to be expected when using a non-generic interface.

Alternatively, you could make a covariant interface IValidatable<out T> interface with a read-only Content property of type T. One wouldn't be able to use the casting operator to extract the content from an IValidatable<Foo> to Foo, but one could simply use the IValidatable<Foo>.Content property.

supercat
  • 77,689
  • 9
  • 166
  • 211