Unknown's avatar

About ericlippert

http://ericlippert.com

Immutability in C# Part Eight: Even More On Binary Trees

Last year we declared a relatively simple interface to represent an immutable binary tree. We noticed that it was different from every other interface that we’ve declared so far, in that it really said nothing at all about the immutability of the tree. One normally thinks of immutable data types not in terms of their shape, but rather in terms of what operations may be performed on the data type. Operations are usually either to query the object somehow, or to “modify” it by producing new modified versions of the old immutable object.

Continue reading

Immutability in C# Part Five: LOLZ!

My sadly soon-to-be-erstwhile coworker Cyrus made me a lolgeek shirt to go with this series of blog articles:

ShirtFront
ShirtBack

Cyrus, needless to say, is a big goof. Thanks, dude!


Commentary from 2022: When I wrote this post 14 years ago Cyrus was about to leave Microsoft and go work for… Google? maybe? I don’t recall. Anyway, after some time we were very fortunate to get him back at Microsoft and last I checked he was once again on the C# compiler team.

Immutability in C# Part Three: A Covariant Immutable Stack

Now suppose we had a hypothetical future version of C# in which interface covariance worked, and we wanted a covariant immutable stack. That is, we want to be able to implicitly convert an IStack<Giraffe> to IStack<Mammal>.

As we’ve already discussed, this doesn’t make much sense in an array, even though doing so is legal in C# today. If you cast a Giraffe[] to Mammal[] then you can try to put a Tiger into the Mammal[] and it will fail at run time. It seems like the same should be true of stacks — if you cast an IStack<Giraffe> to IStack<Mammal> then you can push a Tiger onto the stack of Giraffes, which should fail, right?

Maybe. Maybe not.

Continue reading

Immutability in C# Part Two: A Simple Immutable Stack

Let’s start with something simple: an immutable stack.

Now, immediately I hear the objection: a stack is by its very nature something that changes. A stack is an abstract data type with the interface

void Push(T t);
T Pop();
bool IsEmpty { get; }

You push stuff onto it, you pop stuff off of it, it changes. How can it be immutable?

Continue reading

Immutability in C# Part One: Kinds of Immutability

I said in an earlier post that I believe that immutable objects are the way of the future in C#. I stand by that statement while at the same time noting that it is at this point sufficiently vague as to be practically meaningless! “Immutable” means different things to different people; different kinds of immutability have different pros and cons. I’d like to spend some time over the next few weeks talking about possible directions that C# could go to improve the developer experience when writing programs that use immutable objects, as well as giving some practical examples of the sort of immutable object programming you can do today.

Continue reading

Covariance and Contravariance in C#, Part 3: Method Group Conversion Variance

Last time I discussed how array covariance is broken in C# (and Java, and a number of other languages as well.) Today, a non-broken kind of variance supported by C# 2.0: conversions from method groups to delegates. This is a more complicated kind of variance, so let me spell it out in more detail.

Suppose that you have a method which returns a Giraffe:

static Giraffe MakeGiraffe() { …

Suppose further that you have a delegate type representing a function which takes no arguments and returns an Animal. Say, Func<Animal>. Should this implicit conversion from method group to delegate be legal?

Func<Animal> func = MakeGiraffe;

The caller of func is expecting an Animal to be returned. The actual function captured by the delegate always returns a Giraffe, which is an Animal, so the caller of func is never going to get anything that they’re not capable of dealing with. There is no problem in the type system here. Therefore we can make method group to delegate conversions covariant (‡) in their return types.

Now suppose you have two methods, one which takes a Giraffe and one which takes an Animal:

void Foo(Giraffe g) {}
void Bar(Animal a) {}

and a delegate to a void-returning function that takes a Mammal:

Action<Mammal> action1 = Foo; // illegal
Action<Mammal> action2 = Bar; // legal

Why is the first assignment illegal? Because the caller of action1 can pass a Tiger, but Foo cannot take a Tiger, only a Giraffe! The second assignment is legal because Bar can take any Animal.

In our previous example we preserved the direction of the assignability: Giraffe is smaller than Animal, so a method which returns a Giraffe is smaller than a delegate which returns an Animal. In this example we are reversing the direction of the assignability: Mammal is smaller than Animal, so a method which takes an Animal is smaller than a delegate which takes a Mammal. Because the direction is reversed, method group to delegate conversions are contravariant in their argument types.

Note that all of the above applies only to reference types. We never say something like “well, every int fits into a long, so a method which returns an int is assignable to a variable of type Func<long>”.


Next time on FAIC: a stronger kind of delegate variance that we could support in a hypothetical future version of C#.


(‡) A note to nitpickers out there: yes, I said earlier that variance was a property of operations on types, and here I have an operation on method groups, which are typeless expressions in C#. I’m writing a blog, not a dissertation; deal with it!


Archive of original post