Stretching Type Inference

Note: Code in italics is not actual C# 3.0 syntax.


Local Variable Type Inference


C# 3.0 brought us local variable type inference mainly because of LINQ. The output of a query can vary from an IEnumerable<T> or an IQueryable<T> to a single instance of T where T can even be a projection which means that its type is an anonymous type.


Take the following query as an example:

from p in persons
select new { Name = p.FirstName + " " + p.LastName, Age = p.Age };

If persons is an IEnumerable<Person>, the output of the query will be an IEnumerable<T> where T is an anonymous type.


Now the question that arises is: How do I declare a variable to receive the output of the query?


The solution was the introduction of the new var keyword that instructs the compiler to determine the type of the output of the query. Now, all it takes to declare a variable to receive the output of the query is:

var q =
from p in persons
select new { Name = p.FirstName + " " + p.LastName, Age = p.Age };

Since the compiler can infer the type of something that looks so complex, it's obvious that it can infer the type of something so straightforward as:

var v = new Dictionary<Point, Stack<Person>>();

It's obvious that this is the same as:

Dictionary<Point, Stack<Person>> v = new Dictionary<Point, Stack<Person>>();

with less typing and margin for typing errors.


Field Type Inference


The same technique could be applied to field inference:

class Class
{
private someDicionary = new Dictionary<string, List<string>>();

// ...
}


with the same benefits of less typing and margin for typing errors.


If field type inference were to be applied to public fields, one constraint must be enforced though: the inferenced type cannot be or have as a type parameter an anonymous type.


But I wouldn't recommend that because it would be easy and not obvious that a small change in the field initialization could bring unobvious changes to the public interface of the class.


Method Output  And Parameter Type Inference


Since type inference would be possible outside of methods, should it be allowed on method output and parameter values? Something like this:

class SomeClass
{
private listOfStuff = new List<Stuff>();

public void DoIt()
{
if (CreateList(out this.listOfStuff))
{
ProcessList(ref this.listOfStuff);

this.listOfStuff = TransformList(this.listOfStuff);
}
}

private bool CreateList(var out list)
{
list = new List<Stuff>();
return true;
}

private void ProcessList(var ref list)
{
// ...
}

private var TransformList(var list)
{
return list;
}
}


which look very strange and confusing, to say the least. We shouldn't go there.


Constructor Inference


Every since I saw local variable type inference the first time, I have the feeling that more could have been done.


Let's look at this hypothetical set of declarations:

Dictionary<Point, Stack<Person>> v = new();
Point p = new(1, 2);

It's as easy to the compiler or a human reading the code that this is the same as:

Dictionary<Point, Stack<Person>> v = new Dictionary<Point, Stack<Person>>();
Point p = new Point(1, 2);

And this can be more powerful than local variable type inference and used in a lot more scenarios:

class Class
{
private Dictionary<string, List<string>> someDicionary = new();
private Point p;

public Class()
{
this.p = new(1, 1);
}

public void SomeMethod()
{
AnotherMethod(new());
}

private void AnotherMethod(List<string> arg)
{
// ...
}
}


Where To Stop?

The compiler could as easily infer the type of a local variable in this:

public void SomeMethod()
{
var v = new();
AnotherMethod(v);

}

Do we want to go there? I don't think so.


Ambiguities

C# 3.0 also brought object initializers. Instead of this:

XmlReaderSettings settings = new XmlReaderSettings();
settings.CheckCharacters = false;
settings.IgnoreWhitespace = true;

XmlReader reader = XmlReader.Create("someURI", settings);


we can just write this:

XmlReaderSettings settings = new XmlReaderSettings() { CheckCharacters = false, IgnoreWhitespace = true };

XmlReader reader = XmlReader.Create("someURI", settings);


or this:

XmlReader reader = XmlReader.Create("someURI"new XmlReaderSettings() { CheckCharacters = false, IgnoreWhitespace = true });

Wouldn't it be nice to just write:

XmlReaderSettings settings = new() { CheckCharacters = false, IgnoreWhitespace = true };

C# 3.0 object initializers also allow constructor parenthesis to be omitted:

XmlReaderSettings settings = new XmlReaderSettings { CheckCharacters = false, IgnoreWhitespace = true };

which would lead to:

XmlReaderSettings settings = new { CheckCharacters = false, IgnoreWhitespace = true };

which looks exactly like an anonymous object creation expression and could lead to some confusion and ambiguities.


But it would be nice to just write:

XmlReader reader = XmlReader.Create("someURI", new { settings.CheckCharacters = false, settings.IgnoreWhitespace = true });

 


Wouldn't it?

4 Responses to Stretching Type Inference

  • Bart says:

    Hi Paulo,

    Some nice thoughts but there seem to be tons of things that make this rather difficult to say the least.

    My main concern is the fact that a sole “new()” doesn’t have a type as all expressions in C# do today. This would require type information to flow in the opposite direction, which it never did and IMO never should. It’s similar to why LINQ has the from clause first in order to make the type information flow naturally.

    To point out a concrete problem:

       public void SomeMethod()
       {
           AnotherMethod(new());
       }
       private void AnotherMethod(List<string> arg)
       {
           // …
       }

    will break as soon as you get new conflicting overloads for AnotherMethod which makes reasoning about code really hard. Every time you add a method you risk breaking something somewhere else (luckily you didn’t make it public :-)). Also refactoring becomes trickier, e.g. when you decide to switch a “type-inferred field” from a concrete class to an abstract class or interface.

    With things like:

      Point p = new(1, 1);

    and

      Dictionary<Point, Stack<Person>> v = new();

    I can somewhat symphatize, and you could get rid of the ” = new” portion in there but then we get dangerously close to C’s syntax which would at the very least be confusing because of semantic differences.

    Cheers,

    -Bart

  • Hi Bart,

    I just let my mind run wild from something that looked nice and useful to the scary places it could lead us to. 🙂

  • Justin Lee says:

    With regards to the “Method Output And Parameter Type Inference”, technically we could already do so with Generic Methods and Generic Parameters.

       private bool CreateList<T>(out T list)
       {
          list = new T();
          return true;
       }
       private void ProcessList<T>(ref T list)
       {
             // …
       }
       private T TransformList<T>(T list)
       {
          return list;
       }

    Or something along those lines.

    And might I add it probably would be syntactically correct if this line:    

       private listOfStuff = new List<Stuff>();

    was like this instead:

       private var listOfStuff = new List<Stuff>();

    But definitely, I would like to see “var” being stretched to its limits in terms of type inference. It would be interesting to see how much we can infer statically, instead of going dynamic.

  • In fact, some type inference occurs when you don’t specify the type parameter, but it’s because you already did it somewhere before.

    I’ve also considered “private var” instead of just “private” but came to the conclusion that it’s not needed.

    When defining a local variable “var” is needed to desambiguate if you are defining a variable or assigning a previous defined variable or parameter, which doesn’t occur when defining a field. In fact, there are people that think “var” should not exist, but I don’t agree with that.