4 ms·
Out parameters are also useful in constructors. I often use them as a way to limit the scope in which an object can be modified, which lets me have mostly immut
by noblethrasher 10y ago
Out parameters are also useful in constructors. I often use them as a way to limit the scope in which an object can be modified, which lets me have mostly immutable objects.
IEnumerable<Student> GetStudents(SqlCommand cmd)
{
var rdr = cmd.ExecuteReader(); //select * from student left join courses on...
var memo = new Dictionary<int, Student.Completion>(); //Completion lets us add courses to a student
while(rdr.Read())
{
if(!memo.TryGetValue(rdr.GetInt32("student_id"), var out complete)
memo.Add(new Student(rdr out complete).ID, complete);
complete.AddCourse(rdr); //might be a no-op
}
return from kv in memo select kv.Value.Student; //each student object is unmodifiable.
}
- 16bytes 10y agoSo I'm taking a look at this line: if(!memo.TryGetValue(rdr.GetInt32("student_id"), var out complete) memo.Add(new Student(rdr out complete).ID, complete); I think I understand your point, but I found this code really hard to read. You don't use the first var out complete in the TryGetValue, right? And the Student c'tor returns itself as an out parameter? If I understand you correctly, you like this because you don't have to declare a new student before you add it? I.e. the alternative would be if(!memo.ContainsKey(rdr.GetInt32("student_id"))) var student = new Student(rdr); memo.Add(student.ID, student); I guess I would prefer this over the former. Also, why not enforce your student ID constraint in SQL instead of putting everything in a dictionary only to take it back out again? That would simplify your code to the point of just being a map from rdr->Student. Furthermore, if all I had was the Student c'tor, I would never guess that was the intent of the out parameter. This seems more anti-pattern than pattern. I would rather put a simple IEnumerable in front of SqlDataReader so that you could just do: foreach(var row in rdr) yield return new Student(row) This doesn't obviate your use case, however, which is to inline a variable where it's needed in multiple places in that line because you can save yourself an explicit declaration. In this case, however, I think that the increased readability justifies the explicit declaration.
- noblethrasher 10y agoHi, thanks for taking the time to comment. > You don't use the first var out complete in the TryGetValue, right? There is only one complete variable; we declare it in TryGetValue, and it's definitely used in the last line of the while statement, but might first be used after the if statement. > And the Student c'tor returns itself as an out parameter? The Student constructor does not return itself as an out parameter, rather it returns an object that can modify an internal list of courses. The idea is that when we iterate through a result set from a database, some of the rows are going to correspond to a new student object, and some are going to correspond to a course that belongs to the student. Crucially, we are only allowed modify the Student object (or whatever) during iteration, and the collection that we return will only contain immutable/unmodifiable Student objects. I've used this technique to populate deeply nested structures (lots of joins and nested joins) using only one query. > I would rather put a simple IEnumerable in front of SqlDataReader so that you could just do: > foreach(var row in rdr) yield return new Student(row) Just to be clear, the reason that I cannot do that is because the Student object might not be completely "hydrated" until we finish iterating through the result set, because it might contain a bunch of nested objects that also need to be instantiated from one or more DB records. > Also, why not enforce your student ID constraint in SQL instead of putting everything in a dictionary only to take it back out again? I'm not quite sure I understand this (which is probably my fault), but rest assured that we use nothing but SQL (specifically, DDL) to enforce data integrity.
- 16bytes 10y agoThank you, in turn, for replying. I'm of the opinion that out variables have little value if you have tuples and deconstruction, but I wanted to understand your point. I'm not sure I do, unfortunately. I'm little out of practice with C#, so bear with me. Back to this code: if(!memo.TryGetValue(rdr.GetInt32("student_id"), var out complete) memo.Add(new Student(rdr out complete).ID, complete); There's a parens missing on the end of the if, correct? Also I can't parse the student c'tor: new Student(rdr out complete) I was assuming there is a comma in there somewhere. Does this compile? What does "out" do here? I thought you were getting a new out variable, but that's not correct since you wouldn't be able to name it the same in the same scope. Also, I don't see how "complete" is ever non-null. If the ID isn't in the dictionary, then TryGetValue returns false and "complete" is null. Then you add the null "complete" to the dictionary, and throw away the Student object (which apparently does other side effects) once you have its id? If ID is in the dictionary, you get back what you inserted, which is still null. And then you call .AddCourse on the possibly null reference? I'm lost. Can you post the code again? Maybe I'm just missing something due to a syntax error.