-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
Problem: In many cases we developers still have to use constructor to initialize values, just because we don't have access to "this" in field initializers. Example of expected behavior:
public class MyGraph
{
public class Node
{
public Node(MyGraph parent)
{ ...}
}
public Node Root {get;} = new Node(this);
}
This makes constructor a "semantic bottleneck" and trashes it up with some unrelated pieces of code that should be there near those fields declaration.
Foreseeing the question about the order of field initializers execution and how to know which fields of "this" are already initialized and can be used to see what to expect being initialized. Here is an example that demonstrates situation:
public class MyClass
{
DbConnection connection = new DbConnection();
string[] Names {get;} = this.connection.LoadNames(); //assumes "connection" to be initialized
}
I see 2 approaches:
a) Developer friendly way (Preferable): Execute initializers following the field declaration order. So if "connection" declared before "Names", then during "Names" initialization, connection is available. People can ask what to do in case of partial classes. I would suggest: in case of partial class initializers execution order matches the order of filenames, because that's how compiler will join those files.
//file1.cs
partial class MyClass
{
string[] Names3 {get;} = this.connection.LoadNames(); //NullReference
}
//file2.cs
public partial class MyClass
{
string[] Names {get;} = this.connection.LoadNames(); //NullReference
DbConnection connection = new DbConnection();
string[] Names2 {get;} = this.connection.LoadNames(); //Success
}
//file3.cs
partial class MyClass
{
string[] Names4 {get;} = this.connection.LoadNames(); //Success
}
b) Simple, but hostile way: even though field initializers were executed and values calculated - compiler can just keep fields unassigned until all initializers executed, right before execution of constructor. This would be suckier than the option "a", but at least we'll have link to "this" assigned and can do the workaround using Lazy:
public class MyClass
{
DbConnection connection = new DbConnection();
Lazy<string> Names {get;} = new Lazy<string>(()=>this.connection.LoadNames());
}
Regardless of the approach, having any of those two options would be better than having none, and having to trash up the constructor.
This is definitely doable just on compiler level (no changes to CLR required) and it also does not break existing code, because now devs cannot use "this", all initializers are basically static methods code.