Can one tell me, why the heck does it compile?
namespace ManagedConsoleSketchbook
{
public interface IMyInterface
{
int IntfProp
{
get;
set;
}
}
public class MyClass
{
private IMyInterface field = null;
public IMyInterface Property
{
get
{
return field;
}
}
}
public class Program
{
public static void Method(MyClass #class)
{
Console.WriteLine(#class.Property.IntfProp.ToString());
}
public static void Main(string[] args)
{
// ************
// *** Here ***
// ************
// Assignment to read-only property? wth?
Method(new MyClass { Property = { IntfProp = 5 }});
}
}
}
This is a nested object initializer. It's described in the C# 4 spec like this:
A member initializer that specifies an object initializer after the equals sign is a nested object initializer - that is, an initialization of an embedded object. Instead of assigning a new value to the field or property, the assignments in the nested object initializer are treated as assignments to members of the field or property. Nested object initializers cannot be applied to properties with a value type, or to read-only fields with a value type.
So this code:
MyClass foo = new MyClass { Property = { IntfProp = 5 }};
would be equivalent to:
MyClass tmp = new MyClass();
// Call the *getter* of Property, but the *setter* of IntfProp
tmp.Property.IntfProp = 5;
MyClass foo = tmp;
Because you are using the initializer which uses the setter of ItfProp, not the setter of Property.
So it will throw a NullReferenceException at runtime, since Property will still be null.
Because
int IntfProp {
get;
set;
}
is not readonly.
You did not invoke setter of MyClass.Property, just getter.
Related
This question already has answers here:
Why Collection Initialization Throws NullReferenceException
(4 answers)
Closed 1 year ago.
Why isn't a collection initializer honored in an object initializer?
public class Foo
{
public string[] Bar { get; set; }
public Foo() { }
}
class Program
{
static void Main(string[] args)
{
var foo = new Foo()
{
Bar = { }
};
Assert.Null(foo.Bar); // Null not empty...what gives?
}
}
The expectation is that the underlying array will be set to an initialized empty collection.
That way of initializing a new collection is rewritten internally as Bar.Add() for each element, and since you aren't adding anything to it it will not generate any calls to .Add().
If you decompile that it will look like this (notice how the initialization is lost).
private static void Main(string[] args)
{
new Foo();
}
You can try by actually adding a string to your initialization. It won't compile but show the error below.
var foo = new Foo()
{
Bar = { "" }
};
error CS1061: 'string[]' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string[]' could be found (are you missing a using directive or an assembly reference?)
Now if you initialize it with a new array it will of course work.
var foo = new Foo()
{
Bar = new[] { "" }
};
Checking this dempiled yields this.
private static void Main(string[] args)
{
Foo foo = new Foo();
string[] array = new string[1];
array[0] = "";
foo.Bar = array;
}
So in short, initialize your array in your class constructor to be safe.
It is because default(string[]) is null. You must initialize it, and you could use a constructor for it:
public class Foo
{
public string[] Bar { get; set; }
public Foo()
{
Bar = new string[0];
}
}
In this case, it will instance a new empty array on Bar property.
If a collection initializer is used as part of a member initializer, a new collection will not be instantiated. It is simply syntactic sugar for calling the Add(...) method on the collection.
Note that in this (slightly modified) example, a NullReferenceException is thrown:
public class Foo
{
public List<string> Bar { get; set; }
}
void Main()
{
var foo = new Foo()
{
Bar = { "hello" }
};
}
The C# spec defines this behaviour in section 7.6.10.2:
A member initializer that specifies a collection initializer after the equals sign is an initialization of an embedded collection. Instead of assigning a new collection to the field or property, the elements given in the initializer are added to the collection referenced by the field or property.
I am not sure why you declaring an array this way, you may do what #Felipe Oriani mentioned but I assume you know that and you just trying to understand why your way is not working as it should work. So I will try relate to this and not the best practices of the way of declaring an array.
That's an interesting question as I found some weird things while trying to understand why it happens.
I noticed that if I am initializing it in the constructor with braces, it won't compile (invalid expression term '{'). Not sure why it doesn't compile and the other ways do compile?!
public Foo()
{
Bar = { };
}
Then I mentioned that if I am declaring it not inside an object it works well:
string[] bar = { }; // Creates an empty array of strings
So it would be possible to do it and it will work as you expects:
public class Foo
{
public string[] Bar { get; set; }
public Foo(string[] bar)
{
Bar = bar;
}
}
class Program
{
static void Main(string[] args)
{
string[] bar = { };
var foo = new Foo(bar); // foo.Bar is not empty string array
}
}
C# 5.0 Language Specification 7.6.10.2 Object initializers states that
A member initializer that specifies an object initializer after the
equals sign is a nested object initializer, i.e. an initialization of
an embedded object. Instead of assigning a new value to the field or
property, the assignments in the nested object initializer are treated
as assignments to members of the field or property. Nested object
initializers cannot be applied to properties with a value type, or to
read-only fields with a value type.
While I understand read-only fields cannot be modified by the initializers after the constructor is run, I do not have a clue about the restriction on properties.
The following is a code sample I used to test this restriction on properties:
using System;
namespace ObjectCollectionInitializerExample
{
struct MemberStruct
{
public int field1;
public double field2;
}
class ContainingClass
{
int field;
MemberStruct ms;
public int Field
{
get { return field; }
set { field = value; }
}
public MemberStruct MS
{
get { return ms; }
set { ms = value; }
}
}
class Program
{
static void Main(string[] args)
{
// Nested object initializer applied to a property of value type compiles!
ContainingClass cc = new ContainingClass { Field = 1, MS = new MemberStruct { field1 = 1, field2 = 1.2} };
Console.ReadKey();
}
}
}
I commented on the code where it was supposed to generate a compiler error based on the specification. But it compiles successfully. What am I missing here?
Thanks
What you have is not a nested object initializer because you explicitly create a new instance of MemberStruct. The inner object initializer doesn't directly follow the equals sign, but is an object initializer on its own associated with the call to the MemberStruct constructor.
This is how using a nested object initializer would look like:
ContainingClass cc = new ContainingClass { Field = 1, MS = { field1 = 1, field2 = 1.2} };
This will not compile when MS is a value type (struct), but it will compile when MS is a reference type (object).
What is going on with this C# code? I'm not even sure why it compiles. Specifically, what's going on where it's setting Class1Prop attempting to use the object initializer syntax? It seems like invalid syntax but it compiles and produces a null reference error at runtime.
void Main()
{
var foo = new Class1
{
Class1Prop =
{
Class2Prop = "one"
}
};
}
public class Class1
{
public Class2 Class1Prop { get; set; }
}
public class Class2
{
public string Class2Prop { get; set; }
}
This is allowed by object initializer syntax in the C# specification, where it is called a nested object initializer. It is equivalent to:
var _foo = new Class1();
_foo.Class1Prop.Class2Prop = "one"
var foo = _foo;
It should be a little more obvious why this throws a null reference exception. Class1Prop was never initialized in the constructor of Class1.
The benefit of this syntax is that the caller can use the convenient object initializer syntax even when the properties are getter-only to set mutable properties on nested objects. For example, if Class1Prop was a getter-only property the example is still valid.
Note that there is an inaccessible temporary variable created to prevent the access of a field or array slot before the full initialization has run.
The following testcase throws a null-reference exception, when it tries to assign Id to an object which is null, since the code is missing the "new R" before the object initializer.
Why is this not caught by the compiler? Why is it allowed, in which use-cases would this be a meaningful construct?
[TestClass]
public class ThrowAway
{
public class H
{
public int Id { get; set; }
}
public class R
{
public H Header { get; set; }
}
[TestMethod]
public void ThrowsException()
{
var request = new R
{
Header =
{
Id = 1
},
};
}
}
The compiler doesn't give a warning because you could have:
public class R
{
public H Header { get; set; }
public R()
{
Header = new H();
}
}
so Header could be initialized by someone/something. Solving if someone/something will initialize Header is a complex problem (probably similar to the Halting problem)... Not something that a compiler wants to solve for you :-)
From the C# specifications:
A member initializer that specifies an object initializer after the equals sign is a nested object initializer, i.e. an initialization of an embedded object. Instead of assigning a new value to the field or property, the assignments in the nested object initializer are treated as assignments to members of the field or property. Nested object initializers cannot be applied to properties with a value type, or to read-only fields with a value type.
We are in the case of nested initializer, and see the bolded part. I didn't know it.
Now, note that new R { } is, by C# spec, an 7.6.10.1 Object creation expressions followed by an object-initializer, while the Header = { } is a "pure" 7.6.10.2 Object initializers.
I have a class with a static factory method on it. I want to call the factory to retrieve an instance of the class, and then do additional initialization, preferablly via c# object initializer syntax :
MyClass instance = MyClass.FactoryCreate()
{
someProperty = someValue;
}
vs
MyClass instance = MyClass.FactoryCreate();
instance.someProperty = someValue;
No. Alternatively you could accept a lambda as an argument, which also gives you full control in which part of the "creation" process will be called. This way you can call it like:
MyClass instance = MyClass.FactoryCreate(c=>
{
c.SomeProperty = something;
c.AnotherProperty = somethingElse;
});
The create would look similar to:
public static MyClass FactoryCreate(Action<MyClass> initalizer)
{
MyClass myClass = new MyClass();
//do stuff
initializer( myClass );
//do more stuff
return myClass;
}
Another option is to return a builder instead (with an implicit cast operator to MyClass). Which you would call like:
MyClass instance = MyClass.FactoryCreate()
.WithSomeProperty(something)
.WithAnotherProperty(somethingElse);
Check this for the builder
Both of these versions are checked at compile time and have full intellisense support.
A third option that requires a default constructor:
//used like:
var data = MyClass.FactoryCreate(() => new Data
{
Desc = "something",
Id = 1
});
//Implemented as:
public static MyClass FactoryCreate(Expression<Func<MyClass>> initializer)
{
var myclass = new MyClass();
ApplyInitializer(myclass, (MemberInitExpression)initializer.Body);
return myclass ;
}
//using this:
static void ApplyInitializer(object instance, MemberInitExpression initalizer)
{
foreach (var bind in initalizer.Bindings.Cast<MemberAssignment>())
{
var prop = (PropertyInfo)bind.Member;
var value = ((ConstantExpression)bind.Expression).Value;
prop.SetValue(instance, value, null);
}
}
Its a middle between checked at compile time and not checked. It does need some work, as it is forcing constant expression on the assignments. I think that anything else are variations of the approaches already in the answers. Remember that you can also use the normal assignments, consider if you really need any of this.
Yes. You can use object initializer for already created instance with the following trick. You should create a simple object wrapper:
public struct ObjectIniter<TObject>
{
public ObjectIniter(TObject obj)
{
Obj = obj;
}
public TObject Obj { get; }
}
And now you can use it like this to initialize your objects:
new ObjectIniter<MyClass>(existingInstance)
{
Obj =
{
//Object initializer of MyClass:
Property1 = value1,
Property2 = value2,
//...
}
};
P.S. Related discussion in dotnet repository:
https://github.com/dotnet/csharplang/issues/803
You can use an extension method such as the following:
namespace Utility.Extensions
{
public static class Generic
{
/// <summary>
/// Initialize instance.
/// </summary>
public static T Initialize<T>(this T instance, Action<T> initializer)
{
initializer(instance);
return instance;
}
}
}
You would call it as follows:
using Utility.Extensions;
// ...
var result = MyClass.FactoryCreate()
.Initialize(x =>
{
x.someProperty = someValue;
x.someProperty2 = someValue2;
});
+1 on "No".
Here's an alternative to the anonymous object way:
var instance = MyClass.FactoryCreate(
SomeProperty => "Some value",
OtherProperty => "Other value");
In this case FactoryCreate() would be something like:
public static MyClass FactoryCreate(params Func<object, object>[] initializers)
{
var result = new MyClass();
foreach (var init in initializers)
{
var name = init.Method.GetParameters()[0].Name;
var value = init(null);
typeof(MyClass)
.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase)
.SetValue(result, value, null);
}
return result;
}
No, the object initializer can only be used on a call to "new" with the constructor. One option might be to add some additional args to your factory method, to set those values at object creation inside the factory.
MyClass instance = MyClass.FactoryCreate(int someValue, string otherValue);
Like everyone said, no.
A lambda as an argument has already been suggested.
A more elegant approach would be to accept an anonymous and set the properties according to the object. i.e.
MyClass instance = MyClass.FactoryCreate(new {
SomeProperty = someValue,
OtherProperty = otherValue
});
That would be much slower though, since the object would have to be reflected on for all the properties.
No, that's something you can only do 'inline'. All the factory function can do for you is to return a reference.