Hacker News new | past | comments | ask | show | jobs | submit login

You're right, there were missing tokens; sorry about that.

Here is the equivalent C# 6 version of the code (i.e. something very similar to the pattern that I currently use):

        IEnumerable<Student> GetStudents(SqlCommand cmd)
	{
		var rdr = cmd.ExecuteReader();
		var memo = new Dictionary<int, Student.Completion>();

		while(rdr.Read())
		{
			Student.Completion completion; //this declaration will be unnecessary in C# 7
			
			if(!memo.TryGetValue(rdr.GetInt32("student_id"), out completion))
				memo.Add(new Student(rdr, out completion).ID, completion);
			
			completion.AddCourse(rdr); //completion is *guaranteed* to be non-null
		}
		
		return from kv in memo select kv.Value.Student;
	}
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,

    memo.Add(new Student(rdr, out completion).ID, completion);
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:

     return from kv in memo select kv.Value.Student //`Value` is an instance of Student.Completion
Here is the basic definition of the Student class:

        sealed class Student
	{
		public int ID { get; } //this is a readonly property, meaning it can only be modified in a cstor
		public FullName { get; } //readonly property

		private List<Course> courses = new List<Courses>(); 

                public IEnumerable<Course> Courses => from c in courses select c;
		
		public Student(IDataReader rdr,  out Completion completion)
		{
			ID = rdr.GetInt32("student_id");
			Fullname =  rdr.GetString("fullname");
			
			completion = new Completion(this);
		}
		
		public sealed class Completion
		{
			public Student Student { get; } //readonly property.
			
			public Completion(Student student)
			{
				this.Student =  student;
			}
			
			public void AddCourse(IDataReader rdr)
			{
				if(rdr.GetInt32("student_id) == Student.ID)
					student.courses.Add(new Course(rdr));
			}
		}
	}

 
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).




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?




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: