Thanks for continuing to indulge me in this conversation. So, if I understand you correctly, your data looks something like this:
StudentId| Name |CourseId
============================
1| Bob| 1
2| Jane| 1
1| Bob| 2
1| Bob| 3
2| Jane| 4
With many more columns of course. The point being that it's a denormalized listing of student-course pairs. If so, you'd be able to get immutability by reading the query result into a data table and doing something like: var result = from row in dt.AsEnumerable()
group row by row.Field<int>("StudentId") into grp
select new Student(
grp.Key,
grp.First().Field<string>("Name"),
from courseRow in grp select new Course(courseRow)
);
This seems to satisfy all of the conditions that you list (namely immutable objects), but with the added advantages of not needing a inner Completion class, and a number of less lines of code.Furthermore this is less coupled because now the Student class doesn't need to worry about DataReaders or DataTables or which column names to read from.
I would also argue that this version is a lot easier to read and reason about.
I think you could make a similar transformation for any circumstance in which you wanted to use an out variable in the fashion you outline.
Does that make sense? Is there another case in which you would advocate for our variables in new code?
Here is the equivalent C# 6 version of the code (i.e. something very similar to the pattern that I currently use):
As you can see, it only differs from the C# 7 version by one line.The first thing to note is that, per the C# spec, the `out` parameters of a method must be definitely assigned before the method returns[1]. It just so happens that the constructor of the `Student` class always creates a new `Completion` object and assigns it to the `out` parameter. Now, theoretically, an `out` parameter could be assigned a null reference (as in the case of TryGetValue), but in practice it's trivial to guarantee that it will be non-null (as in the case of our Student cstor).
In the line,
we first call the Student cstor, which assigns a non-null value to completion, so that by the time `memo.Add` is called, the completion variable is guaranteed to be non-null.Also, `Student.Completion` is a class that is defined inside of the `Student` class. As such, it has access to all of Student's members (private and public). But, in order to do anything to an instance of Student, a Completion instance must have field which references that Student instance (unlike the case of Java's inner classes, which are a bit more powerful I think). That is why this line is possible:
Here is the basic definition of the Student class: Like I said in my earlier comment, my immediate goal was to be able to create a set of immutable objects with arbitrary nestings from the result of a single SQL query that may have an arbitrary number of joins (which is how we represent nesting relationally). The above example just has just one nested property, but I have production code in which objects have many more nested properties. For example, the `Course` class in the aforementioned example might have its own `Completion` class for adding `CourseAssignment` instances (e.g. select from Student left join Course on ... left join CourseAssignment on ...).But, the really big idea is that I wanted an object-capability system[2][3]. Getting objects be immutable "almost everywhere"[4] is a nice side benefit.
[1] http://www.ecma-international.org/publications/files/ECMA-ST... (section 12.1.6)
[2] https://en.wikipedia.org/wiki/Object-capability_model
[3] https://www.youtube.com/watch?v=EGX2I31OhBE
[4] https://en.wikipedia.org/wiki/Almost_everywhere (in this case "almost everywhere" is with respect to the set of all possible execution paths).