Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. General Programming
  3. C#
  4. Assignability of output parameters in C#

Assignability of output parameters in C#

Scheduled Pinned Locked Moved C#
11 Posts 3 Posters 0 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • D dojohansen

    Hi all, I'm wondering why C# imposes restrictions on assignments using output parameters that are not otherwise present. Presumably there is some logical reason for it, but I can't think of it. Assume this code:

    public interface SomeInterface { }

    class A : SomeInterface
    {
    public SomeInterface Field;

    void foo() 
    { 
        Parse(out this.Field); 
    }
    
    static public void Parse(out A something)
    {
        something = new A();
    }
    

    }

    This code will not build - instead, the compiler will inform you that The best overloaded method match for A.Parse(out A)' has some invalid arguments. Since A implements SomeInterface, Field is of course assignable to A, but nevertheless it does not build. If I instead write the code so that Parse returns A and the assignment is done in foo(), it builds without as much as a warning, as it should.

    void foo() 
    { 
        Field = Parse(); 
    }
    
    static public A Parse()
    {
        return new A();
    }
    

    This causes some minor annoyances for me as I'm using the compiler-compiler Coco/R to generate a parser for a language of my creation. This tool lets me specify the parser by means of what's called an attributed grammar, and the generated code uses the attributes in the grammar and maps them to parameters. What I wanted to do was to use an output parameter for most productions in the grammar, like this: Term = (. t = new Term(); Factor f; .) Factor (. t.Mul(f); .) { '*' Factor (. t.Mul(f); .) | '/' Factor (. t.Div(f); .) } . This straightforwardly leads to a generated method for parsing a Term that looks like this:

    void Term(out Term t)
    {
    t = new Term(); Factor f;
    Factor(out f);
    t.Mul(f);
    while (la.kind == 15 || la.kind == 16)
    {
    if (la.kind == 15)
    {
    Get();
    Factor(out f);
    t.Mul(f);
    }
    else
    {
    Get();
    Factor(out f);
    t.Div(f);
    }
    }
    }

    My little problem arises because I've modeled Factor as an interface type. I want to be able to later on write a new class that performs some calculation or other, be free to derive this class from any class I wish, and so I've defined a simple interface IScalar { decimal GetValue(); } so that any class implementing it can be used as a factor. I've modeled arithmetic expressions a

    R Offline
    R Offline
    Rob Philpott
    wrote on last edited by
    #2

    I've been staring at your bit of code for about ten minutes now. I feel like I should know the answer to this but I'm not sure I do. Essentially I guess it's that .NET can't do upcasting when using Out parameters and an exact type match is required. Can't you do something with the compiler-compiler not to use this construct. I rather dislike out parameters and try to avoid them? Either that or use a local variable of the correct type then assign the interface type to that?

    Regards, Rob Philpott.

    D 1 Reply Last reply
    0
    • R Rob Philpott

      I've been staring at your bit of code for about ten minutes now. I feel like I should know the answer to this but I'm not sure I do. Essentially I guess it's that .NET can't do upcasting when using Out parameters and an exact type match is required. Can't you do something with the compiler-compiler not to use this construct. I rather dislike out parameters and try to avoid them? Either that or use a local variable of the correct type then assign the interface type to that?

      Regards, Rob Philpott.

      D Offline
      D Offline
      dojohansen
      wrote on last edited by
      #3

      I can't do anything not to use this construct if I want the method parsing a production to instantiate the object - there's no way to specify that I want to use a return type instead of an output parameter. I can use normal in parameters and also ref, but it doesn't solve the problem. I can use a local variable in the parsing method, but then it kind of nullifies the point of using polymorphism - I didn't want to have to write any code for each specific type. It's not really a lot in this case, but I think I'd prefer to just make a method that parses a number "return" (i.e. use an output parameter of type) IScalar rather than Number. The exact same issue exists if I use inheritance instead of interfaces to achieve polymorphism. That is, if a field is of type BaseClass and a method has an output parameter of type DerivedClass, I cannot assign the field using an out parameter. So I need to put the knowledge of what specific type it is in every context it is used. A possible solution would be to omit the parameters completely and make a stateful parser. I could put expressions onto a stack as I parse them (Coco supports "semantic actions", which is any C# code I wish to insert when something is parsed, so I can call custom methods in my parser - this is usally used for things like looking up in symbol tables or even emitting opcodes). Then, when I parse a number, it would find the Factor on the stack and assign the scalar to it. But it's a pity to have to create a stack that basically just recreates the structure that is already present on the call stack! That, after all, is the basic idea behind a recursive-descent parser! I'm asking more out of curiosity (I try to see stuff I don't understand as an opportunity to learn something new) than anything else. The minor friction it creates in the parser code is overcome by pragmatically changing so I use the interface type everywhere, e.g. <code>Number(out IScalar s)</code> instead of <code>Number(out Number n)</code> and I'm good to go. I don't really see any problems with this. If I want to make something like a syntax highlighter later on this logic would anyway have to examine the run-time type of the Scalar field, since this has to accept any type of scalar. Thanks anyway!

      1 Reply Last reply
      0
      • D dojohansen

        Hi all, I'm wondering why C# imposes restrictions on assignments using output parameters that are not otherwise present. Presumably there is some logical reason for it, but I can't think of it. Assume this code:

        public interface SomeInterface { }

        class A : SomeInterface
        {
        public SomeInterface Field;

        void foo() 
        { 
            Parse(out this.Field); 
        }
        
        static public void Parse(out A something)
        {
            something = new A();
        }
        

        }

        This code will not build - instead, the compiler will inform you that The best overloaded method match for A.Parse(out A)' has some invalid arguments. Since A implements SomeInterface, Field is of course assignable to A, but nevertheless it does not build. If I instead write the code so that Parse returns A and the assignment is done in foo(), it builds without as much as a warning, as it should.

        void foo() 
        { 
            Field = Parse(); 
        }
        
        static public A Parse()
        {
            return new A();
        }
        

        This causes some minor annoyances for me as I'm using the compiler-compiler Coco/R to generate a parser for a language of my creation. This tool lets me specify the parser by means of what's called an attributed grammar, and the generated code uses the attributes in the grammar and maps them to parameters. What I wanted to do was to use an output parameter for most productions in the grammar, like this: Term = (. t = new Term(); Factor f; .) Factor (. t.Mul(f); .) { '*' Factor (. t.Mul(f); .) | '/' Factor (. t.Div(f); .) } . This straightforwardly leads to a generated method for parsing a Term that looks like this:

        void Term(out Term t)
        {
        t = new Term(); Factor f;
        Factor(out f);
        t.Mul(f);
        while (la.kind == 15 || la.kind == 16)
        {
        if (la.kind == 15)
        {
        Get();
        Factor(out f);
        t.Mul(f);
        }
        else
        {
        Get();
        Factor(out f);
        t.Div(f);
        }
        }
        }

        My little problem arises because I've modeled Factor as an interface type. I want to be able to later on write a new class that performs some calculation or other, be free to derive this class from any class I wish, and so I've defined a simple interface IScalar { decimal GetValue(); } so that any class implementing it can be used as a factor. I've modeled arithmetic expressions a

        S Offline
        S Offline
        S Senthil Kumar
        wrote on last edited by
        #4

        dojohansen wrote:

        Since A implements SomeInterface, Field is of course assignable to A, but nevertheless it does not build.

        Assuming the compiler allowed that, guess what would happen if someone wrote

        interface ISome {}
        class SomeA : ISome {}
        class SomeB : ISome {}

        void Method()
        {
        SomeA a = new SomeA();
        OutMethod(out a);
        }

        void OutMethod(out ISome some)
        {
        some = new SomeB();
        }

        Boom, a variable typed to be SomeA is now referring to an object of type SomeB, something that is not legal.

        Regards Senthil [MVP - Visual C#] _____________________________ My Home Page |My Blog | My Articles | My Flickr | WinMacro

        D 2 Replies Last reply
        0
        • S S Senthil Kumar

          dojohansen wrote:

          Since A implements SomeInterface, Field is of course assignable to A, but nevertheless it does not build.

          Assuming the compiler allowed that, guess what would happen if someone wrote

          interface ISome {}
          class SomeA : ISome {}
          class SomeB : ISome {}

          void Method()
          {
          SomeA a = new SomeA();
          OutMethod(out a);
          }

          void OutMethod(out ISome some)
          {
          some = new SomeB();
          }

          Boom, a variable typed to be SomeA is now referring to an object of type SomeB, something that is not legal.

          Regards Senthil [MVP - Visual C#] _____________________________ My Home Page |My Blog | My Articles | My Flickr | WinMacro

          D Offline
          D Offline
          dojohansen
          wrote on last edited by
          #5

          For some reason I never got a notification of this reply and I only read it now after tumbling around the "my codeproject" pages. Therefore the great delay in saying Thank you, Sir - it makes perfect sense now! In fact, I should have thought of it. But I did not. :D

          1 Reply Last reply
          0
          • S S Senthil Kumar

            dojohansen wrote:

            Since A implements SomeInterface, Field is of course assignable to A, but nevertheless it does not build.

            Assuming the compiler allowed that, guess what would happen if someone wrote

            interface ISome {}
            class SomeA : ISome {}
            class SomeB : ISome {}

            void Method()
            {
            SomeA a = new SomeA();
            OutMethod(out a);
            }

            void OutMethod(out ISome some)
            {
            some = new SomeB();
            }

            Boom, a variable typed to be SomeA is now referring to an object of type SomeB, something that is not legal.

            Regards Senthil [MVP - Visual C#] _____________________________ My Home Page |My Blog | My Articles | My Flickr | WinMacro

            D Offline
            D Offline
            dojohansen
            wrote on last edited by
            #6

            I spoke too soon. Unfortunately your example doesn't explain anything. The assignment your code example does is unsafe (based on the declared types), and it would not compile if you did the same assignment using return values instead of output parameters. I should have spotted this immediately, because that's another way of posing my original question: Why should output parameters have different assignability than return values? Too see why your example misses the point, consider if we modified your code like this:

            void Method()
            {
            SomeA a = Method2();
            }

            ISome Method2()
            {
            return new SomeB();
            }

            Here, and in your example with the out parameters, we're attempting to assign a field of type SomeA to a reference of type SomeB, which is unsafe. But in my original code the assignment I'm trying to do is known to be safe, as you can verify since the same assignment done from a return value rather than an out parameter builds just fine (and indeed runs fine). Nor has it got anything to do with the fact that I pass a field rather than a local variable; the same issue arises if I use a local. The compiler complains it can't convert the concrete type to the interface type, but it plainly can and does so if I declare the out parameter as having the interface type... It makes no sense to me, and I'm still hoping someone can shed light on this.

            S 1 Reply Last reply
            0
            • D dojohansen

              I spoke too soon. Unfortunately your example doesn't explain anything. The assignment your code example does is unsafe (based on the declared types), and it would not compile if you did the same assignment using return values instead of output parameters. I should have spotted this immediately, because that's another way of posing my original question: Why should output parameters have different assignability than return values? Too see why your example misses the point, consider if we modified your code like this:

              void Method()
              {
              SomeA a = Method2();
              }

              ISome Method2()
              {
              return new SomeB();
              }

              Here, and in your example with the out parameters, we're attempting to assign a field of type SomeA to a reference of type SomeB, which is unsafe. But in my original code the assignment I'm trying to do is known to be safe, as you can verify since the same assignment done from a return value rather than an out parameter builds just fine (and indeed runs fine). Nor has it got anything to do with the fact that I pass a field rather than a local variable; the same issue arises if I use a local. The compiler complains it can't convert the concrete type to the interface type, but it plainly can and does so if I declare the out parameter as having the interface type... It makes no sense to me, and I'm still hoping someone can shed light on this.

              S Offline
              S Offline
              S Senthil Kumar
              wrote on last edited by
              #7

              dojohansen wrote:

              and it would not compile if you did the same assignment using return values instead of output parameters.

              It would compile if you cast the return value to SomeA

              SomeA a = (SomeA) Method2();

              dojohansen wrote:

              But in my original code the assignment I'm trying to do is known to be safe, as you can verify since the same assignment done from a return value rather than an out parameter builds just fine (and indeed runs fine).

              I guess I used the wrong example - here's what you are trying to do

              interface ISome {}
              class SomeA : ISome {}
              class SomeB : ISome {}

              void Method()
              {
              ISome some = SomeB();
              Method2(out some);
              }

              void Method2(out SomeA some) // ISome is NOT SomeA
              {
              some = new SomeA();
              }

              The problem here is ISome need not refer to SomeA, it could refer to any class that implements ISome, but Method2 is expecting SomeA. In fact, the code wouldn't compile even if you removed the out keyword. The code would compile if you changed the parameter of Method2 to out ISome some instead. Casting to SomeA when calling Method2 would also work, but not if the parameter is out. The variable you're passing as out needs to be assignable, and the cast expression is not assignable. And about it working for return types, welcome to the world of covariance and contravariance[^]. Return values are covariant, method parameters are contravariant, which means that return values could be more derived than what the caller expects, whereas method parameters could only be equal or less derived than the caller's parameters.

              Regards Senthil _____________________________ My Home Page |My Blog | My Articles | My Flickr | WinMacro

              D 1 Reply Last reply
              0
              • S S Senthil Kumar

                dojohansen wrote:

                and it would not compile if you did the same assignment using return values instead of output parameters.

                It would compile if you cast the return value to SomeA

                SomeA a = (SomeA) Method2();

                dojohansen wrote:

                But in my original code the assignment I'm trying to do is known to be safe, as you can verify since the same assignment done from a return value rather than an out parameter builds just fine (and indeed runs fine).

                I guess I used the wrong example - here's what you are trying to do

                interface ISome {}
                class SomeA : ISome {}
                class SomeB : ISome {}

                void Method()
                {
                ISome some = SomeB();
                Method2(out some);
                }

                void Method2(out SomeA some) // ISome is NOT SomeA
                {
                some = new SomeA();
                }

                The problem here is ISome need not refer to SomeA, it could refer to any class that implements ISome, but Method2 is expecting SomeA. In fact, the code wouldn't compile even if you removed the out keyword. The code would compile if you changed the parameter of Method2 to out ISome some instead. Casting to SomeA when calling Method2 would also work, but not if the parameter is out. The variable you're passing as out needs to be assignable, and the cast expression is not assignable. And about it working for return types, welcome to the world of covariance and contravariance[^]. Return values are covariant, method parameters are contravariant, which means that return values could be more derived than what the caller expects, whereas method parameters could only be equal or less derived than the caller's parameters.

                Regards Senthil _____________________________ My Home Page |My Blog | My Articles | My Flickr | WinMacro

                D Offline
                D Offline
                dojohansen
                wrote on last edited by
                #8

                I'm afraid you are still not getting my question. I'm fully aware that it doesn't compile if I use the specific type that the function actually assigns to the out parameter as the declared type of that parameter. But my point is that I see absolutely no reason why it should not. Again, if you simply frame the issue as "why should (output/ref) parameters have different assignability compared to return values?" it should become clear that your reply does not answer the question. One detail while I'm at it: When using the output parameter it makes little sense to initialize the variable; just declare it. But let's not go off topic on that one. :)

                S 1 Reply Last reply
                0
                • D dojohansen

                  I'm afraid you are still not getting my question. I'm fully aware that it doesn't compile if I use the specific type that the function actually assigns to the out parameter as the declared type of that parameter. But my point is that I see absolutely no reason why it should not. Again, if you simply frame the issue as "why should (output/ref) parameters have different assignability compared to return values?" it should become clear that your reply does not answer the question. One detail while I'm at it: When using the output parameter it makes little sense to initialize the variable; just declare it. But let's not go off topic on that one. :)

                  S Offline
                  S Offline
                  S Senthil Kumar
                  wrote on last edited by
                  #9

                  Hmm. This was your original code sample.

                  public interface SomeInterface { }

                  class A : SomeInterface
                  {
                  public SomeInterface Field;

                  void foo() 
                  { 
                      Parse(out this.Field); 
                  }
                  
                  static public void Parse(out A something)
                  {
                      something = new A();
                  }
                  

                  }

                  You're asking why the compiler doesn't allow this.Field to be passed as an output parameter to Parse, which takes a type that implements SomeInterface as a parameter, right? Whereas it works if Parse returns an instance of A and foo stores it in this.Field, right?

                  dojohansen wrote:

                  Again, if you simply frame the issue as "why should (output/ref) parameters have different assignability compared to return values?"

                  No, that's not the issue. The issue is that method parameters have different assignability compared to return values. It is because method parameters and return values have different rules when it comes to what type they can be. Regardless of the fact that out parameters are used to simulate return values, they are method parameters to the language, and are therefore subject to method parameter rules. The(simplified) rules are 1. Return values can be more derived than what the caller expects.

                  ISomeInterface isi = Parse(...);

                  Parse(...) {}

                  2. Method parameters should be the same or less derived than what the caller is passing

                  Parse(this.Field);

                  void Parse( something){}

                  A little thinking will tell you why the rules exist.

                  ISomeInterface isi = new A(); // OK
                  A a = isi; // NOT OK

                  By passing this.Field to Parse, you are trying to do what the second statement in the above snippet is trying to do, and that's why the compiler is not allowing it. Using a return value has semantics similar to the first statement, and that's why it works. Hope this helps :)

                  Regards Senthil _____________________________ My Home Page |My Blog | My Articles | My Flickr | WinMacro

                  D 1 Reply Last reply
                  0
                  • S S Senthil Kumar

                    Hmm. This was your original code sample.

                    public interface SomeInterface { }

                    class A : SomeInterface
                    {
                    public SomeInterface Field;

                    void foo() 
                    { 
                        Parse(out this.Field); 
                    }
                    
                    static public void Parse(out A something)
                    {
                        something = new A();
                    }
                    

                    }

                    You're asking why the compiler doesn't allow this.Field to be passed as an output parameter to Parse, which takes a type that implements SomeInterface as a parameter, right? Whereas it works if Parse returns an instance of A and foo stores it in this.Field, right?

                    dojohansen wrote:

                    Again, if you simply frame the issue as "why should (output/ref) parameters have different assignability compared to return values?"

                    No, that's not the issue. The issue is that method parameters have different assignability compared to return values. It is because method parameters and return values have different rules when it comes to what type they can be. Regardless of the fact that out parameters are used to simulate return values, they are method parameters to the language, and are therefore subject to method parameter rules. The(simplified) rules are 1. Return values can be more derived than what the caller expects.

                    ISomeInterface isi = Parse(...);

                    Parse(...) {}

                    2. Method parameters should be the same or less derived than what the caller is passing

                    Parse(this.Field);

                    void Parse( something){}

                    A little thinking will tell you why the rules exist.

                    ISomeInterface isi = new A(); // OK
                    A a = isi; // NOT OK

                    By passing this.Field to Parse, you are trying to do what the second statement in the above snippet is trying to do, and that's why the compiler is not allowing it. Using a return value has semantics similar to the first statement, and that's why it works. Hope this helps :)

                    Regards Senthil _____________________________ My Home Page |My Blog | My Articles | My Flickr | WinMacro

                    D Offline
                    D Offline
                    dojohansen
                    wrote on last edited by
                    #10

                    Hi, Thanks for trying to help out. I think I get it. I disagree with your claim that my code is trying to perform that illegal assignment. "Field" is of the interface type (the less derived type), and the out parameter is of the concrete type that implements the interface type. The only assignment that would ever be attempted if that code ran (forgetting for a second the fact that it does not compile!) is the first of your two assignments, which is indeed legal. In C# an out parameter cannot be used in the method declaring it unless that method has assigned it; it is considered unassigned, just like any local variable that has been declared but not initialized. Because of this "strictly one-way" behavior, an out parameter *could* logically speaking support the same assignability rules as return values. A ref parameter on the other hand could not, since it can be used in both directions, thus resulting in a downcast (to a more derived type) that is implicit yet unsafe. But then I thought for a moment about what the compiler would actually do with output and ref parameters, and it occured to me that it probably generates exactly the same IL for both! After all, the only difference is that you're not allowed to use the out parameter in the declaring method, and you are required to always assign it, both of which can be verified by the compiler but result in the same IL code (or no IL code at all if changing a ref parameter to an out parameter results in a build error - changing an out to a ref however will never cause a build error). I think I've therefore arrived at the following conclusion: 1) There are no reasons at the abstract level why a formal out parameter could not be more derived than it's corresponding actual parameter. If the language allowed callers to use foo(out obj) where obj is declared as "object", no unsafe casting would ever result regardless of how derived the formal parameter is, e.g. foo(out int n) or foo(out CryptoStream s). 2) There are some (probably pretty good) practical reasons why parameters should all follow the same rules. If they do, formal parameters must necessarily be less derived than actual ones.

                    S 1 Reply Last reply
                    0
                    • D dojohansen

                      Hi, Thanks for trying to help out. I think I get it. I disagree with your claim that my code is trying to perform that illegal assignment. "Field" is of the interface type (the less derived type), and the out parameter is of the concrete type that implements the interface type. The only assignment that would ever be attempted if that code ran (forgetting for a second the fact that it does not compile!) is the first of your two assignments, which is indeed legal. In C# an out parameter cannot be used in the method declaring it unless that method has assigned it; it is considered unassigned, just like any local variable that has been declared but not initialized. Because of this "strictly one-way" behavior, an out parameter *could* logically speaking support the same assignability rules as return values. A ref parameter on the other hand could not, since it can be used in both directions, thus resulting in a downcast (to a more derived type) that is implicit yet unsafe. But then I thought for a moment about what the compiler would actually do with output and ref parameters, and it occured to me that it probably generates exactly the same IL for both! After all, the only difference is that you're not allowed to use the out parameter in the declaring method, and you are required to always assign it, both of which can be verified by the compiler but result in the same IL code (or no IL code at all if changing a ref parameter to an out parameter results in a build error - changing an out to a ref however will never cause a build error). I think I've therefore arrived at the following conclusion: 1) There are no reasons at the abstract level why a formal out parameter could not be more derived than it's corresponding actual parameter. If the language allowed callers to use foo(out obj) where obj is declared as "object", no unsafe casting would ever result regardless of how derived the formal parameter is, e.g. foo(out int n) or foo(out CryptoStream s). 2) There are some (probably pretty good) practical reasons why parameters should all follow the same rules. If they do, formal parameters must necessarily be less derived than actual ones.

                      S Offline
                      S Offline
                      S Senthil Kumar
                      wrote on last edited by
                      #11

                      Yeah, the issue is that out is simply an attribute affixed to a parameter, it doesn't affect type checking in any way. As you said, the error goes away if rewrite Parse to take the interface as the out parameter (instead of the concrete type). You only lose the ability to specify that the method sets instances of type A (or one of its derived types) to the out parameter. Great discussion though :)

                      Regards Senthil _____________________________ My Home Page |My Blog | My Articles | My Flickr | WinMacro

                      1 Reply Last reply
                      0
                      Reply
                      • Reply as topic
                      Log in to reply
                      • Oldest to Newest
                      • Newest to Oldest
                      • Most Votes


                      • Login

                      • Don't have an account? Register

                      • Login or register to search.
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • World
                      • Users
                      • Groups