locked
implicit conversion operator question

    Question

  • I don't understand why I would get CS0553 on this code.    The docs imply that the error should exist when you are trying to convert from Foo to object, not the other way around as this code is doing.  Can anyone enlighten me?

    Thanks,
    Brian


    public class Foo
    {
       public Foo( object x )
       {
          this.x = x;
       }

       static public implicit operator Foo(object x)
       {
          return new Foo(x);
       }

       private object x;
    }

     



    e:\temprojects\CS0553issue\Program.cs(13,23): error CS0553: 'CS0553issue.Foo.implicit operator CS0553issue.Foo(object)': user-defined conversion to/from base class

    Monday, November 21, 2005 4:47 PM

Answers

  • Basically, you are trying to overshadow the cast semantics of reference types - there already is an (explicit) conversion from object to Expr and it would be very weird if you were allowed to mess with that. Think about code such as


    if (o is Expr) e = (Expr)o;
     


    This would suddenly have a whole new meaning! Also consider that every single value would be convertible, via object, to Expr!

    We have taken the liberty of predefining conversions (casts) between base classes and derived classes, and to make the semantics of the language predictable we don't allow you to mess with it. It is a little restrictive perhaps, but we believe that many more subtle errors than well-architected programs lie down that path.

    In general I would recommend that you use conversions only between different representations of the "same" thing - especially when they are implicit. While it is kind of nifty that you can make all sorts of things happen behind the scenes just by assigning between specific types, it can also quickly lead to write-only code that is very hard to decipher, for someone else, or for yourself in 6 months time.

    If you need to do advanced stuff like creating different kinds of Exprs based on the type or state of the input, then a factory based approach could be your thing. A static factory, where a family of possibly overloaded static makeExpr methods provide the creation functionality, is generally more flexible and robust than using constructors directly.

    Hope that gets you going, and thanks for exercising the corners of our language!

     

    Wednesday, November 23, 2005 7:05 AM

All replies

  • It's saying that you can't write user defined conversions to and from a base class.

    I think the reasoning behind this is that a conversion already exists, so you don't need one:


    object o = new Foo();

    Foo foo = (Foo)o;

     


    Monday, November 21, 2005 9:13 PM
  • Just to let you know; I've asked a Microsoft C# team member to respond.
    Tuesday, November 22, 2005 11:19 AM
  • Thanks, David.

    I'm using implicit operators to wrap the object type, and the fact that the object type itself is a base class isn't relevant.  It's an "Expr"ession type that is expected (for now) to wrap strings, doubles, ints, other Expr, and sometimes polymorphically though object.  It's the last case that requires that the "user" of Expr uses a different syntax.

    As far as I can tell, there's no strong reason why the compiler should not accept Expr expr = o any less than Expr expr = "a" or Expr expr = 1;

    Brian
    Tuesday, November 22, 2005 1:38 PM
  • Basically, you are trying to overshadow the cast semantics of reference types - there already is an (explicit) conversion from object to Expr and it would be very weird if you were allowed to mess with that. Think about code such as


    if (o is Expr) e = (Expr)o;
     


    This would suddenly have a whole new meaning! Also consider that every single value would be convertible, via object, to Expr!

    We have taken the liberty of predefining conversions (casts) between base classes and derived classes, and to make the semantics of the language predictable we don't allow you to mess with it. It is a little restrictive perhaps, but we believe that many more subtle errors than well-architected programs lie down that path.

    In general I would recommend that you use conversions only between different representations of the "same" thing - especially when they are implicit. While it is kind of nifty that you can make all sorts of things happen behind the scenes just by assigning between specific types, it can also quickly lead to write-only code that is very hard to decipher, for someone else, or for yourself in 6 months time.

    If you need to do advanced stuff like creating different kinds of Exprs based on the type or state of the input, then a factory based approach could be your thing. A static factory, where a family of possibly overloaded static makeExpr methods provide the creation functionality, is generally more flexible and robust than using constructors directly.

    Hope that gets you going, and thanks for exercising the corners of our language!

     

    Wednesday, November 23, 2005 7:05 AM
  • Thank you for your detailed reply.  I'm indeed trying out the corners of the language.  What I'm doing is wrapping the MathLink.NET component that talks to Mathematica, and I'm having pretty good success so far.

    Example:
    Expr a = "x^2";
    Expr b = a *  "x^3";
    Expr c = b;
    Console.WriteLine( c );

    I'm lazy-evaluating the expressions, to keep the slow Mathematica requests to a minimum  Privately in Expr, I'm building a strings (e.g. c.value is "(x^2)*(x^3)") and an Expr.ToString override will lazily evaluate with a call to Mathematica and substitute "x^5").

    I can also do things like (ExprList)"{1,2,3}".Apply("Log") which evaluates to "{Log[1],Log[2],Log[3]}.  But I'm still fleshing out the lazy evaluation rules for lists.  If you have any tips, I'm ears.

    The current issue I'm having is that I'm overriding ToString() to trigger the lazy evaluation, since Console.WriteLine calls it through object.  Seems reasonable, but it also is a headache.  (i.e. The debugger even causes calls to ToString().)

    Brian

    Wednesday, November 23, 2005 3:49 PM
  • Hi there, I know this is old post but the best one I found.

    I want to ask something related to subject (cast from base to derived class).

    I have 2 dlls. One has base class and another derived class that has some extra variables attached. Then I operate with several large List<base>. I need to convert them to List<derived>, but I am wondering is there way to convert them and not produce duplicates in memory? In other words to reuse instances of base class for values of derived class and just add extra values later.

    Tuesday, June 12, 2007 9:07 AM
  • You can only do so if you use composition instead of inheritance.

     

    Instead of

    Code Snippet

    class A

    {

       object state;

    }

    class B : A

    {

       object additionalState;

    }

     

     

    do the following:

    Code Snippet

    class A

    {

       object state;

    }

    class B

    {

       A core;

       object additionalState;

    }

     

     

    Tuesday, June 12, 2007 11:50 AM
  • Ok but doesn't that just totally destroy the whole reason for inheritance, Thomas?

    I used your example alot in VB6, but I'm trying to make my code a bit smaller by avoiding such validations in C# as ..

    public int Foo ()
    {
       if (core == null)
       // throw error

      return core.someValue;
    }

    =================================
    The example that MS uses for inheritance follows with one minor change. I'm not creating a new Circle or Square, but instead creating a new shape and then casting it to a circle or square.  This should work, but instead if fails.

    class Shape
    { .. code .. }
     
    class Circle : Shape
    { .. code .. }
     
    class Square : Shape
    { .. code .. }
     
    class Main
    {
     Main (args ..)
      {
        Shape a1 = new Shape(x,y,z);
        Circle B1 = (Circle) a1;
        Square C1 = (Square) a1;
      }
    }
     
    Without explicit conversion I get a runtime exception error.
    With explicit conversion I get Compiler Error CS0553.
     
    How do I resolve this issue?

    Monday, July 16, 2007 5:02 AM
  • This code should not work. a1 is no Circle so you can't convert it to a circle.

    "Up-casts" are only valid if the corresponding object is of the type you're casting to.

    Code Snippet
    Shape a1 = new Circle(x,y,z);
    Circle B1 = (Circle) a1;

     

     


    Monday, July 16, 2007 7:30 AM
  • Thomas,

     

    Though I agree that the code does not work.  I also feel this solution if very flawed:

     

    class Circle : Shape

    {

      public Circle (Shape newShape)

      {

        base.pX = newShape.pX;

        base.pY = newShape.pY;

      }

    }

     

    class Main

    {

      Shape A1 = new Shape (1,1);

      Circle B1 = new Circle (A1);

    }

     

    I'm vested in finding an elegant solution becuase I'm working with heirarchial databases that subclass the base class.  At present I use methods like Circle's Public Construction Method to consume the Base object when creating a SubClass and basically copy the values into the Base object. 

     

    Any suggestions?

     

     

    Monday, July 16, 2007 2:31 PM
  • This compiler behavior really should be considered as a great BUG.

    Of course we do not need and must not redefine type widening cast but we MUST have an option to redefine a type narrowing cast.

     

    Consider this example:

     

    class A

    {...}

     

    class Child1A : A

    { ... }

     

    class Child2A : A

    {...}

     

    class BA : A

    {...}

     

    class Child1BA : BA

    {...}

     

    class Child2BA : BA

    { ... }

     

    class Target : A

    {...}

     

    With this class layout we can easily define both explicit and implicit casts from BA (and all BA's children (Child1BA and Child2BA)  to Target.

     

    So we can cast any branch of A children (such as BA) to Target (that is a child of A itself), but we cannot cast ALL brances and children of A to Target using one single method. We have to write separate casts for  Child1A, Child2A etc... But what if any of subclasses is out of scope? We can't cast them.

     

    That's bad.

    May be the REAL reason is that cast definitions are only possible for otherwise invalid casts?

    Friday, August 24, 2007 9:46 AM
  •  Sergey E. Kolesnikov wrote:

    This compiler behavior really should be considered as a great BUG.

    Of course we do not need and must not redefine type widening cast but we MUST have an option to redefine a type narrowing cast.

     

    Consider this example:

     

    class A

    {...}

     

    class Child1A : A

    { ... }

     

    class Child2A : A

    {...}

     

    class BA : A

    {...}

     

    class Child1BA : BA

    {...}

     

    class Child2BA : BA

    { ... }

     

    class Target : A

    {...}

     

    With this class layout we can easily define both explicit and implicit casts from BA (and all BA's children (Child1BA and Child2BA)  to Target.

     

    So we can cast any branch of A children (such as BA) to Target (that is a child of A itself), but we cannot cast ALL brances and children of A to Target using one single method. We have to write separate casts for  Child1A, Child2A etc... But what if any of subclasses is out of scope? We can't cast them.

     

    That's bad.

    May be the REAL reason is that cast definitions are only possible for otherwise invalid casts?

    What you're asking to do are not casts, they are conversions.  You could conceivably convert anything derived from A to Target, but you'd have to lose all the non-A data; in which case you'd never be able to go back to the original.

    For example:

    Code Snippet

    class A {

      public int value;

    }

    class B : A {

      public int mode;

    }

    class C : A {

      public int mean;

    }

    class Target : A {

      public int average;

    }

     

     

    The compiler has no clue what to do to Target.average during a conversion from C to Target or B to Target.  Yes, it knows how to get value from C/B to Target, the but rest of Target would be undefined and therefore it can't automatically perform a conversion.

     

    You can move down the inheritance hierarchy but not across with casts.

     

    Since there's loss of data in that conversion the compiler isn't going to provide that functionality automatically, it demands that you explicitly define how that conversion needs to be done as well as explicitly request the conversion.

     

    So, no, it's not a bug.  Post as a suggestion to http://connect.microsoft.com/visualstudio if you want a direct response from someone from Microsoft.

    Friday, August 24, 2007 3:01 PM
  • I think, even if we consider above post as explanation why casting from base type to derived type is not implemented (but arguable), we still have serious compiler BUG.

    Why?

    Because we can write code that do explicit cast from A (base type) to B (derived type), for example:

     

    A a = new A();

    a.Id = 1;

    a.Name = "Base";

    B b = (B)a;

     

    and... compiler will compile program. Program will crush only at runtime!

     

    Another problem is here:

     

    "Since there's loss of data in that conversion the compiler isn't going to provide that functionality automatically, it demands that you explicitly define how that conversion needs to be done as well as explicitly request the conversion."

     

    If you define EXPLICIT conversion in type A that has destination B (derived class) like this for example:

     

    public static explicit operator B(A a)

    {

    B b = new B();

    b.Id = a.Id;

    b.Name = a.Name;

    }

     

    Compiler will throw you error (at least it will not crush at runtime like above example):

     

    'A.explicit operator B(A)': user-defined conversion to/from derived class A.cs..."

     

    Something is definitely wrong here, or somebody has reasonable explanation for both things?

    It looks as serious compiler BUG to me.

    Friday, August 24, 2007 4:04 PM
  • Although it may not seem ideal in this case, but if you explicitly cast, the compiler assumes you know what you're doing when you explicitly ask it to do that and doesn't generate a compile error.  If you just look at this line:

    Code Snippet
      B b = (B)a;

     

     

     

    The compiler can't know that a isn't a B because you could have done this:

    Code Snippet

    A a = new B();

    B b = (B)a;

     

     

    Friday, August 24, 2007 4:13 PM
  • I agree completely. That is exactly contrary from what was stated before. I also see that you skipped to comment another side of problem (that compiler doesn't allow me to define explicit conversion for this case).

    It is said above that we have situation with casting like this to avoid confusion in code, it can be hard to decipher etc...

    Well... I think that what we have now is confusing, hard to decipher and we have exception that only in this case we are not able to make our custom explicit conversion operator, while normally for other situation we can.

    Quite confusing isn't it.

     

    Then, explanation that preventing cast from base type A to it's derived classes B and C A->B and A->C is there to avoid lost of data when we convert from B->C is arguable. It is actually B->A->C where we explicitly "confirm" that we want to loose extra data from B. if we need data from be again... C->A->B and there it is. Same logic like in above example...

     

    A a = new B();

    B b = (B)a;

     

    Where is our extra data from B class when we do "A a = new B(); "?

    As far as I know, instance of B in memory is stored like instance of A + extra memory space for extended data from B.

    That why we are able to cast back from B to A (it's already in memory like that).

    Then why not have cast to C class that is actually same memory space of A + extra space for C extra data. If we need our B, back we cast back to A and then to B. Visually something like this:

     

    [.....A.....] [..extra data for B...][...extra data for C...]

     

    After all, logic is 100% same as what we have now... Casting from B->A where we "loose" extra data and then by casting back we get our B back as whole.

    Friday, August 24, 2007 4:38 PM
  •  Nenad Vicentic wrote:

    I agree completely. That is exactly contrary from what was stated before. I also see that you skipped to comment another side of problem (that compiler doesn't allow me to define explicit conversion for this case).

    It is said above that we have situation with casting like this to avoid confusion in code, it can be hard to decipher etc...

    Well... I think that what we have now is confusing, hard to decipher and we have exception that only in this case we are not able to make our custom explicit conversion operator, while normally for other situation we can.

    Quite confusing isn't it.

    When you confuse casts and conversions, yes you can confuse yourself.  The concept isn't confusing though.  You're not able to override a built-in cast because that would be silly.  Where B derives from A, you can't create an implicit or explict operator to or from a base type.  That could override a cast with a conversion, in which case the expectation is that a new object will be the result of the conversion, not the same object.  Yes, you could use the same object, but the fact remains that a new object *could* be the result of an implicit or explicit operator, so you can't create a conversion to/from a base type.

     

     Nenad Vicentic wrote:
    Then, explanation that preventing cast from base type A to it's derived classes B and C A->B and A->C is there to avoid lost of data when we convert from B->C is arguable. It is actually B->A->C where we explicitly "confirm" that we want to loose extra data from B. if we need data from be again... C->A->B and there it is. Same logic like in above example...

    It's not a cast, it's a conversion.  You can't cast form an A object to a B object, you can only convert; in which case it likely creates a new object and therefore it's not round-trip.

     Nenad Vicentic wrote:

    A a = new B();

    B b = (B)a;

     

    Where is our extra data from B class when we do "A a = new B(); "?

    That is a cast, the a variable is still a reference to a B object.

     

     Nenad Vicentic wrote:
    As far as I know, instance of B in memory is stored like instance of A + extra memory space for extended data from B.

    That why we are able to cast back from B to A (it's already in memory like that).

    Then why not have cast to C class that is actually same memory space of A + extra space for C extra data. If we need our B, back we cast back to A and then to B. Visually something like this:

     

    [.....A.....] [..extra data for B...][...extra data for C...]

     

    After all, logic is 100% same as what we have now... Casting from B->A where we "loose" extra data and then by casting back we get our B back as whole.

    No, you don't lose data by casting, you potentially lose data with conversions.

    Friday, August 24, 2007 4:59 PM
  • Shortly, you are saying that when I do like this:

     

    A a = new B();

    B b = (B)a;

     

    First I cast B to A, and then I convert A to B (creating new instance)? (I think not)

     

    Or you are telling me that this line:

     

    B b = (B)a;

     

    can mean both casting and conversion, depending on other lines in program?

    Even compiler is not able to recognize what it is?

    That's why we succesfully compile and get runtime error?

     

    *We assume in all examples that A is base class and B derived class.

    Friday, August 24, 2007 5:17 PM
  •  Nenad Vicentic wrote:

    Shortly, you are saying that when I do like this:

     

    A a = new B();

    B b = (B)a;

     

    First I cast B to A, and then I convert A to B (creating new instance)?

    If I said you can't define an user-defined conversion operator that converts from or to a base class then no, I did not say that.  Those are both casts, not conversions, in the context of B deriving from A.

     Nenad Vicentic wrote:

    Or you are telling me that this line:

     

    B b = (B)a;

     

    can mean both casting and conversion, depending on other lines in program?

    Even compiler is not able to recognize what it is?

    That's why we succesfully compile and get runtime error?

     

    *We assume in all examples that A is base class and B derived class.

    No, B b = (B)a can never be a conversion as long as B derives from A (or A derives from B).

     

    C c = (C)a; can be a conversion as long as C is not related to A in any way.

    Friday, August 24, 2007 5:37 PM
  •  Peter Ritchie wrote:

     Nenad Vicentic wrote:

    Or you are telling me that this line:

     

    B b = (B)a;

     

    can mean both casting and conversion, depending on other lines in program?

    Even compiler is not able to recognize what it is?

    That's why we succesfully compile and get runtime error?

     

    *We assume in all examples that A is base class and B derived class.

    No, B b = (B)a can never be a conversion as long as B derives from A (or A derives from B).

     

    No, B b = (B)a can never be a conversion as long as B derives from A (or A derives from B)????

     

    But you wrote also this:

     Peter Ritchie wrote:

    It's not a cast, it's a conversion.  You can't cast form an A object to a B object, you can only convert; in which case it likely creates a new object and therefore it's not round-trip.

     

    So, what it is?

    It can never be conversion, or it can only be conversion?

     

    Friday, August 24, 2007 6:00 PM
  •  Nenad Vicentic wrote:
     Peter Ritchie wrote:

     Nenad Vicentic wrote:

    Or you are telling me that this line:

     

    B b = (B)a;

     

    can mean both casting and conversion, depending on other lines in program?

    Even compiler is not able to recognize what it is?

    That's why we succesfully compile and get runtime error?

     

    *We assume in all examples that A is base class and B derived class.

    No, B b = (B)a can never be a conversion as long as B derives from A (or A derives from B).

     

    No, B b = (B)a can never be a conversion as long as B derives from A (or A derives from B)????

     

    But you wrote also this:

     Peter Ritchie wrote:

    It's not a cast, it's a conversion.  You can't cast form an A object to a B object, you can only convert; in which case it likely creates a new object and therefore it's not round-trip.

     

    So, what it is?

    It can never be conversion, or it can only be conversion?

     

    Sorry, to be more clear: you cannot cast from an A object to a B object you can only convert it with some conversion method, not a conversion operator.  If "A a = new A(); B b = (B)a;" were syntactically correct, it would be a conversion not a cast.
    Friday, August 24, 2007 6:07 PM
  •  Peter Ritchie wrote:

    If "A a = new A(); B b = (B)a;" were syntactically correct, it would be a conversion not a cast.

     

    In case where: "A a = new A(); B b = (B)a;", and it works as conversion - B is not derived class from A. So, I don't know why you invoked that argument at first place.

     

    For the rest, I agree with you.

     

    Where we dissagree is, if this behaviour is very bad and confusing, like meny people here stated, or it's best solution to avoid confusion.

    Considering that we also confused each other, I doubt that this is clean solution for problem.

    Friday, August 24, 2007 7:35 PM
  • I would generally avoid conversion operators, particularly in such complex and confusing scenarios like described in this thread. Till now, I've never had the need to write my own conversion operator. IMO they're only useful in casting from base classes to derived classes (though they are no conversion operators in this case) or in converting bigger numeric values to smaller once. You should use conversions/casts only if you know that the conversion/cast will be successful (and the compiler isn't able to know it) and they should be simple enough to be able to tell what's going on without knowing the implementation of the operators (what wouldn't be the case in the scenario discussed here). You can always use a method to do the conversion with the benefit that you can give it some meaningful name. The code gets more readable and so the developers using the code will be more productive.

    That's my opinion to conversion operators.

     

    Monday, August 27, 2007 6:19 AM
  • > Basically, you are trying to overshadow the cast semantics of reference types - there already is an (explicit) conversion from object to Expr and it would be very weird if you were allowed to mess with that.

    In fact, there is still a possibility to do that.

    Code Snippet

    using System;

    class Expr<T>
    {
        public static implicit operator Expr<T>(T x)
        {
            return new Expr<T>();
        }
    }

    class Program
    {
        static void Main()
        {
            Expr<object> a = new object();
            Expr<object> b = true;
            Expr<object> c = 1.23;
        }
    }


    Friday, August 31, 2007 11:24 AM