Wednesday, December 10, 2008

C# odditiy: Object initializer scope

I was reviewing some code today and ran across something that looked like this:
SomeProperty = SomeProperty

Odd, I thought that doesn't do anything, probably not what the writer intended. I sort of ignored it since I was doing some refactoring that removed that line anyway and went on. When I realized I didn't have any tests covering this bit of code, I saved my changes, reverted the unit, wrote a test and ran it, expecting a failure. Much to my surprise, it worked.

In context, the function looked sort of like this:
public class SomeClass
{
    public string SomeProperty { get; set; }

    public object Clone()
    {
        return new SomeClass { SomeProperty = SomeProperty };
    }
}

Looking at this, I expected both the SomeProperty properties to be in the scope of the object initializer and end up not doing anything, essentially assigning the property to itself. The test seemed to indicate otherwise. Thinking I had a problem with my test, I set some breakpoints and did some evaluation at run-time. I didn't see anything wrong with the tests and everything seemed to be working.

Still convinced something had to be wrong, I did some searching on the web trying to find somebody who talked about scoping rules in object initializers. Of course, I may not have hit on the correct search terms, but in any case I didn't find anything. So I wrote the following console application:
namespace TestObjectInitializerScopeConsole
{
    public class TestClass
    {
        public string TestText { get; set; }
    }

    class Program
    {
        public static string TestText { get; private set; }

        static void Main(string[] args)
        {
            TestText = "Some random text";
            Console.WriteLine("this.TestText = \"{0}\" (initially)", TestText);

            var test1 = new TestClass();
            Console.WriteLine("test1.TestText = \"{0}\" (without initializer)", test1.TestText);

            var test2 = new TestClass { TestText = TestText };
            Console.WriteLine("test2.TestText = \"{0}\" (with initializer)", test2.TestText);

            Console.WriteLine("Enter to finish...");
            Console.ReadLine();
        }
    }
}

According to this test, it seems to indicate the scope for items on the left hand of the assignment are the new object and those on the right hand are what you'd normally expect for the method. It seems pretty odd and anti-intuitive to have different scope like this.

Any comments? Am I missing something? Is this defined somewhere that I've missed? Does it seem weird to anyone else?

3 comments:

Anonymous said...

As I understood the object initializer structure to be visualized, it does make sense.
The right hand side is evaluated in the context of the new call and passed to the property assignment occurring after creation.

It looks really strange the way you had it written and certainly looks confusing.

Harley Pebley said...

Thanks for the comment Anonymous.

"The right hand side is evaluated in the context of the new call and passed to the property assignment occurring after creation."

If I understand what you're saying, you think the right side evaluation is done before the "new" call with the scope of the current method. These values are then passed to the object's properties.

This is certainly consistent with the behavior. IMO, it is completely inconsistent with the syntax though. Everywhere else in C# (that I can think of) braces indicate a scope. In this case, the typical scoping rules are violated.

Eric Lippert said...

> Is this defined somewhere that I've missed?

Perhaps you missed checking section 7.5.10.2 of the language specification. That's where it is defined. :)

> Everywhere else in C# (that I can think of) braces indicate a scope.

Braces do not define a scope in array initializers, object initializers or anonymous type initializers. There are also lots of scopes that do not involve braces.