1

I have a simple class, which looks something like this:

public class MyClass<T> 
{
    public int Status { get; set; }
    public T Value { get; set; }
}

For convenience, I have another class which inherits from MyClass<string>, to allow me to construct MyClass without generic arguments, such as:

public class MyClass : MyClass<string> { }

I want to be able to cast MyClass<T> to MyClass, and it seems this doesnt work by default. This example cast throws the following error:

MyClass<string> withT = new MyClass<string> { Status = 1, Value = "Somevalue" };
MyClass withoutT = (MyClass)withT;

Unable to cast object of type 'MyClass`1[System.String]' to 'MyClass'

So I believe I need to implement some implicit/explicit casting logic, as described in this answer.

I've updated MyClass<T> as follows, however the same error is still thrown:

public class MyClass<T>
{
    public int Status { get; set; }
    public T Value;

    public static implicit operator MyClass(MyClass<T> myClass)
    {
        return new MyClass
        {
            Status = myClass.Status,
            Value = myClass.Value.GetType() == typeof(string) ? myClass.Value.ToString() : null
        };
    }
}

Can anyone help point out where I'm going wrong?

devklick
  • 2,000
  • 3
  • 30
  • 47
  • 5
    You've created an instance of `MyClass`. That *isn't* an instance of `MyClass`. It's like trying to do: `object x = new object(); string y = (string) x;`. – Jon Skeet Dec 16 '19 at 12:11
  • How non-generic `MyClass` is defined? – Pavel Anikhouski Dec 16 '19 at 12:11
  • 1
    Every `MyClass` is a `MyClass` but not every `MyClass` is a `MyClass`. – DavidG Dec 16 '19 at 12:12
  • 1
    In terms of why the conversion is failing - normally you can't define conversions within a type hierarchy at all, but I believe generics is effectively hiding the problem here - because if you try to convert from a `MyClass` to a `MyClass` then it *would* work. But I'd suggest rethinking your `MyClass` non-generic type entirely. A using alias is more likely to be useful, if this is really just a matter of "I want to construct a `MyClass` without having to specify the type arguments". – Jon Skeet Dec 16 '19 at 12:14
  • Note that the conversion will give you fairly unexpected behavior, as you'd be creating a new, independent instance - whereas readers may well expect it to just be a simple cast, retaining object identity. – Jon Skeet Dec 16 '19 at 12:17
  • I understand that instances of `MyClass` are not instances of `MyClass`, but thought the whole point of implementing a casting method was to define rules around how one can be "changed" to become another. – devklick Dec 16 '19 at 12:20
  • You're not allowed to define conversion operators that move up or down the inheritance hierarchy. Those movements are handled by reference reinterpretation and is not subject to intervention by the types involved. You'll get a compiler error like `"UserQuery.MyClass.implicit operator object(UserQuery.MyClass)': user-defined conversions to or from a base class are not allowed`. As @JonSkeet is pointing out, the fact that you defined it generically probably "confuses" the compiler so it doesn't report this as the problem, but it is. – Lasse V. Karlsen Dec 16 '19 at 12:33
  • Basically, if you do `var x = (BaseClass)derivedObject;` no conversion operators will be involved, nor the opposite way. – Lasse V. Karlsen Dec 16 '19 at 12:33
  • You have several options, conversion method in `MyClass` (bad since it will be available for all T's, not just strings), constructor or factory method in `MyClass`, or an extension method on `MyClass` (which would be my choice). So you would do `MyClass mc = mcs.ToMyClass();` or similar. And no, you cannot (yet) declare a static extension operator. – Lasse V. Karlsen Dec 16 '19 at 12:36
  • What's the purpose of `MyClass` anyways? I'm guessing that you left out some details for brevity, but it's not clear why you _need_ to cast to `MyClass` rather then just using the object as a `MyClass`. – D Stanley Dec 16 '19 at 13:53
  • `public static implicit operator MyClass(MyClass myClass)` is not needed since `MyClass` is derived from `MyClass`. – John Alexiou Dec 16 '19 at 16:46

3 Answers3

1

Jon Skeet is right in his comments.

Throw out generics altogether. Can you do this?

class X {}
class Y : X {}
...
var x = new X();
var y = (Y)x;

No. You cannot. An X is not a Y. Similarly a MyClass<string> is not a MyClass.

Taking it further your casting code still doesn't work as you've stated, and trying to define an implicit or explicit cast operator in other ways (e.g. typed specifically with string) won't even compile.

Solutions

  • Do l33t's solution. It's the most straight forward.
  • Do @ja72's solution. It works and is the most generic.
  • Create your own static methods that do conversion or create extension methods that do the conversion.
  • Break the inheritance of MyClass from MyClass<string> and define your operators. Yes, that means some redundant code.
Kit
  • 20,354
  • 4
  • 60
  • 103
  • I understand that `X` is not a `Y`, however I thought the whole point of casting was to use `X` to create a `Y` (or vis versa), regardless of their relation. – devklick Dec 18 '19 at 23:46
  • Er, I wouldn't say the *whole point* is to do conversion. Rather, casting is a type-safe way of making an instance of some type suitable for use in a different context. For example, HTTP status codes are an integer, but using an enum to represent them is programmatically convenient, and sometimes you must convert between the two types. – Kit Dec 19 '19 at 00:53
0

Why not use a factory? Here's a very simple example:

public static class MyClass
{
    public static MyClass<string> Create(string s, int status)
    {
        return new MyClass<string>(s, status);
    }
}

...

var x = MyClass.Create("Foo", 0);
l33t
  • 18,692
  • 16
  • 103
  • 180
0

Consider making MyClass<T> a must inherit class (with the abstract keyword) so it cannot be instantiated. This way you can only create objects from derived classes, which you can down cast to the base class.

Also if you want a type reference to the derived types (for casting) then include that in the type parameters. Add a TDerived which much inherit from MyClass<T> as such:

public abstract class MyClass<TDerived, TValue> where TDerived : MyClass<TDerived, TValue>
{
    protected MyClass(int status, TValue value)
    {
        this.Status = status;
        this.Value = value;
    }
    public int Status { get; set; }
    public TValue Value { get; set; }

    public static implicit operator TDerived(MyClass<TDerived, TValue> myClass)
        => myClass as TDerived;
}

public class SpecificClass : MyClass<SpecificClass, string>
{
    public SpecificClass(int status, string value) : base(status, value)
    {
        // The ONLY way to instantiate a MyClass<> is from a derived class
        // such as this.
    }        
}

class Program
{
    static void Main(string[] args)
    {
        MyClass<SpecificClass, string> my_class = new SpecificClass(-1, "ABC");

        SpecificClass my_specific = my_class;
    }
}
John Alexiou
  • 28,472
  • 11
  • 77
  • 133