The changing state of immutability C#
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Number of Parts | 133 | |
Author | ||
License | CC Attribution - NonCommercial - ShareAlike 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this | |
Identifiers | 10.5446/48841 (DOI) | |
Publisher | ||
Release Date | ||
Language |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
00:00
Software developerForceTime evolutionData modelRead-only memoryBoolean algebraPoint (geometry)Spectrum (functional analysis)Execution unitPower (physics)Suite (music)Visualization (computer graphics)Point (geometry)Slide rulePresentation of a groupString (computer science)CASE <Informatik>BlogObject (grammar)BitCategory of beingC sharpCodeSystem callData typeSemiconductor memoryQuicksortType theoryWindowOrder (biology)Representation (politics)Computer fileSeries (mathematics)MultilaterationCommunications protocolPattern languageBuffer solutionLiquidCovering spaceBranch (computer science)Row (database)FreezingField (computer science)Revision controlMathematicsDecision theoryTerm (mathematics)NumberFormal languageSpectrum (functional analysis)Social classMultiplication signWeightEndliche ModelltheorieProjective planeGraph coloringGastropod shellAddress spaceRoutingData modelNormal (geometry)Musical ensembleLetterpress printingGoogolReading (process)JSONXMLUMLComputer animation
08:50
Software developerString (computer science)Equals signOvalStatisticsFluid staticsSystem callState observerComputer programmingThread (computing)VarianceNeuroinformatikConstructor (object-oriented programming)Software bugMultiplication signMathematicsGoodness of fitArithmetic meanFlow separationField (computer science)Type theoryDefault (computer science)Hash functionSocial classCodeOperator (mathematics)Term (mathematics)Spectrum (functional analysis)Reading (process)Object (grammar)Inheritance (object-oriented programming)CounterexampleImplementationData dictionaryString (computer science)Descriptive statisticsJava appletInterior (topology)Information securityState of matterWeightEndliche ModelltheorieBitMessage passingPoint (geometry)2 (number)Category of beingParameter (computer programming)Right angleSign (mathematics)Sound effectPasswordKey (cryptography)Computer animation
17:34
Software developerFlagFluid staticsPhysical systemLie groupAbstractionCASE <Informatik>Right angleMultiplication signType theory1 (number)Social classMathematicsGoodness of fitConstructor (object-oriented programming)Physical systemObject (grammar)AbstractionInstance (computer science)Moment (mathematics)Compilation albumCodeRevision controlTouch typingBounded variationVisualization (computer graphics)ChainOvalC sharpInheritance (object-oriented programming)Pattern languageInterior (topology)Computer animation
21:40
Software developerString (computer science)Standard deviationCodeStrutThread (computing)OvalComputer virusSlide ruleTerm (mathematics)Type theoryCategory of beingCodeBitQuicksortState of matterSocial classVariable (mathematics)Process (computing)Library (computing)Mixture modelJava appletDifferent (Kate Ryan album)NumberService (economics)Generic programmingMultiplication signGraph coloringString (computer science)Equaliser (mathematics)CompilerSpectrum (functional analysis)Parameter (computer programming)Phase transitionReading (process)Instance (computer science)Video game consoleScripting languageRegular expressionLine (geometry)Group actionType theoryThread (computing)Operator (mathematics)MathematicsNatural numberWeightInterior (topology)Computer virusLambda calculusBuffer solutionCausalityComputer animation
29:51
Default (computer science)Category of beingString (computer science)Type theorySoftware developerSlide ruleOvalObject (grammar)Parameter (computer programming)Sound effectQuery languageSet (mathematics)Lambda calculusType theoryHash functionC sharpCategory of beingQuery languageLine (geometry)Equaliser (mathematics)Regular expressionMotion captureTerm (mathematics)Field (computer science)Interior (topology)Multiplication signObject (grammar)Java appletSoftware developerCodeJust-in-Time-CompilerString (computer science)Social classSoftware maintenanceFlow separationVarianceRight angleRead-only memoryGeneric programmingThread (computing)NumberAlgebraic closureLink (knot theory)Game theoryCountingVariable (mathematics)Similarity (geometry)Key (cryptography)Electronic mailing listImplementationReading (process)Process (computing)Graph coloringPoint (geometry)Device driverUniform boundedness principleDifferent (Kate Ryan album)MathematicsSystem callArithmetic meanEquivalence relationSequelState of matterCASE <Informatik>Computer animation
38:02
Software developerQuery languageExecution unitSlide ruleLine (geometry)String (computer science)Object (grammar)Internet forumParameter (computer programming)Type theoryDefault (computer science)Type theoryObject (grammar)C sharpDefault (computer science)Query languageLink (knot theory)Cycle (graph theory)Video gameParameter (computer programming)Constructor (object-oriented programming)Message passingBitComputer configurationProcess (computing)MultilaterationWritingLibrary (computing)CASE <Informatik>Poisson-KlammerInstance (computer science)Electronic mailing listCategory of beingOcean currentGoodness of fitCodeString (computer science)Point (geometry)Social classProgrammer (hardware)Graphics tabletFunctional (mathematics)Right angleSpecial unitary groupWordComputer virusSound effectSystem callCuboidMultiplication signHost Identity ProtocolLattice (order)Revision controlSelectivity (electronic)Computer animation
44:07
Internet forumSoftware developerReading (process)Default (computer science)Uniformer RaumAddress spaceNumberType theoryDifferent (Kate Ryan album)CodeType theoryBitMultiplication sign2 (number)Operator (mathematics)Lambda calculusTerm (mathematics)Statement (computer science)Constructor (object-oriented programming)NumberAddress spacePoint (geometry)Message passingString (computer science)DampingRegular expressionGraphics tablet1 (number)Interior (topology)Functional (mathematics)Product (business)Field (computer science)Telephone number mappingData modelCodeCategory of beingDefault (computer science)QuicksortSummierbarkeitExtension (kinesiology)Total S.A.Link (knot theory)Computer fileCASE <Informatik>Single-precision floating-point formatReading (process)Dressing (medical)Principal idealProfil (magazine)Equaliser (mathematics)RecursionExpert systemSequelDatabaseSpur <Mathematik>Right angleSineSemiconductor memorySystem callComputer animation
50:12
String (computer science)Software developerAddress spaceFormal languageInversion (music)Multiplication signForm (programming)Message passingSlide ruleBuildingSemiconductor memoryEndliche ModelltheorieField (computer science)Point (geometry)Electronic mailing listConstructor (object-oriented programming)Category of beingSocial classHierarchyRevision controlSoftware developerInformation overloadMathematicsWeightParameter (computer programming)Buffer solutionTerm (mathematics)Formal languageHash functionObject-oriented programmingType theoryOperator (mathematics)Interface (computing)Object (grammar)Structural loadSystem callFunctional (mathematics)Communications protocolInstance (computer science)Pattern languageLibrary (computing)Run time (program lifecycle phase)Order (biology)NumberCodeSelf-organizationCountingInheritance (object-oriented programming)Address spaceEmailComputer configurationMereologyMoment (mathematics)Row (database)BitAbstractionData conversionMathematical analysisRight angleMixture modelAxiom of choicePerimeterDressing (medical)Disk read-and-write headFunctional programmingBounded variationData typeComputer programmingThomas BayesDesign by contractWebsiteCASE <Informatik>Reading (process)WordAirfoilLevel (video gaming)Arithmetic meanResultantSingle-precision floating-point formatProcess (computing)Office suiteComputer animation
Transcript: English(auto-generated)
00:06
Today, I'm talking about immutability, which is a topic very close to my heart. I started trying to work out what a PowerPoint presentation might look like, and there would be so much chopping and changing between PowerPoint and Visual Studio
00:20
that I decided to do the slides in Visual Studio as text files. Importantly, can everyone read this? These bits are more notes for me than for you, but there is, of course, code as well, and hopefully everyone can see the code. That's rather more important.
00:40
Okay, I should introduce myself in case you haven't caught either of the other talks. My name is John Skeat. I work for Google here in London. I am not talking on behalf of Google, so if I say stupid things, please blame me rather than Google. Done. We're going to be talking today about immutability, partly because I like it as a topic,
01:02
partly because I wouldn't say fighting against it, but fighting to implement it in a project called Protocol Buffers and struggling with trying to find code that is nice and readable but still works well for immutability in C Sharp.
01:20
Just to cut a long story short, on that front, I gave up, and the new version of Protocol Buffers for C Sharp is entirely with mutable messages, because we decided that the code ended up looking more idiomatic. If, by any chance, C Sharp 7 encourages more immutability and makes immutable types more the norm,
01:42
then we may well revisit that decision with probably a side branch of, you know, you could use this build or this build of Protocol Buffers. But that's just sort of how I'm into this in particular. I'll talk a little bit about what it means to be immutable and how it's not nearly as simple as it sounds,
02:01
why we might want our data types to be immutable, what language features, either obviously or slightly more subtly, have influenced how you code immutability in C Sharp. I will have a brief, hopefully, rant about the memory model
02:21
in .NET and C Sharp, give you various examples. I think I have five examples using the same common model, data model, of how we might go for an immutable type with a person, address, phone number, and phone number kind.
02:42
Talk a little bit about why it's difficult, and then give a few hints about what the future might look like in terms of C Sharp 7, and a bit more than just C Sharp 7. I haven't used C Sharp 7 at all yet myself,
03:00
nor have I even attended talks given by members of the C Sharp team about C Sharp 7. I've read Rosalind issues and things and read some design notes. Bill will be talking immediately after this talk in room, which room are you in, Bill? Room 2. I assume that was just telling me which room it was in.
03:22
In room 2, about C Sharp 7. That will cover record types and immutability, and it will also cover a whole host more, and Bill's a fantastic speaker, so go to his session. Okay, let's kick off my next slide. I mentioned that if we're going to talk about immutability,
03:43
we should really know what we mean by it. I seem to be talking about Eric Lippert a lot today, but he has a blog post, or a whole series of blog posts about immutability, kind of focusing on immutable collections, which I hardly touched today, but defining various kinds of immutability.
04:04
And we should really see what kind of spectrum this is. So I have a few examples that are really just very simple. Here is a mutable class, and it's mutable because I can change what's stored in it.
04:24
For those of you in the last talk where I had the idea of a house representation with a number of windows and the color of a front door, if you couldn't add windows to a house and you couldn't repaint its front door, it would be immutable. If you can, then it's mutable. So this is really obviously mutable,
04:45
because aside from anything else, we've got a property with a setter. I will at various points in this talk use C Sharp 6. Apologies if that confuses you at all, if you're still unfortunately stuck with C Sharp 5. Just shout out if you need anything explained.
05:01
But this has been around since C Sharp 3. So we can see that it's mutable here. We can change m.value. Okay, everyone would agree that's mutable. Here's a slightly more subtle class. I've called it shallow mutable because all our properties only have getters.
05:24
So these are C Sharp 6 read-only automatically implemented properties. Effectively think of a read-only field. So is that mutable or immutable? Well, we can't change anything in the object itself.
05:44
If you looked at the object in memory, it's not going to change. But it's not usefully immutable because you can change things chained onto it. So one of the properties is a string builder
06:02
and string builders are fairly famously mutable. That's the whole point of them that they are mutable. So if I create a shallow mutable using an empty string builder and then I don't even need to use the new shallow mutable object in order to sort of change it,
06:21
I can append foo to my string builder and then this time I'm going via the shallow mutable and I append bar via the m.name builder and then if I print out m.name builder, I end up with foo bar. Okay, everyone comfortable so far?
06:40
Yep, okay. So in some ways maybe this should be called shallowly immutable but it's effectively mutable because it's so shallow. Here's an example which I sometimes think of as a nice pattern and usually think of as an anti-pattern.
07:02
Popsicle immutability. This phrase was definitely coined, I first heard, by Eric Lippert. The idea is it's like a popsicle US thing. You get a plastic, not plastic bag, but a plastic pocket that's full of a liquid
07:21
and you buy it in the shop and it's still liquid and you put it in your freezer and it sets and while it's still liquid, you can bend these things and that's all fine. And then when you take it out of the freezer, it's frozen so you can't change it anymore. So this is precisely popsicle immutability.
07:41
So we have a single object here and we create it, we mutate it, we then freeze it and we can't mutate it anymore. There are issues with popsicle immutability. It feels nice in one sense,
08:05
partly because you end up with just one type, but because you end up with one type, if you accept one of these, you can tell whether or not it's frozen, but if you're given one of these as a parameter, can you hold onto it in your object for later use?
08:22
Well, you probably shouldn't freeze it yourself unless that's what your method says it's going to do because that's kind of rude. If someone says, I will create a new popsicle, set the value to something, call a method and pass it on and then set it again. Oh, it's gone bang. Why has it gone bang?
08:40
Oh, that method decided that it wanted to freeze it on my behalf. That's kind of rude. And you end up littering your code with, oh, if it's already frozen, I'll handle it this way. Otherwise, I'll take a copy and then freeze my copy and then I can be assured that it's immutable and kind of hope that the threading model doesn't get involved anywhere
09:01
because that would be bad. I'll come on to threading later. So, popsicle immutability. Observable immutability, this is now looking a lot better. So here, our observably immutable class only has a name property. The type is string, or a public name property.
09:22
The type is string, which we will assume is an immutable type in whatever way would be useful for the purposes of the example. I know that in Java, string is observably immutable rather than genuinely fully immutable.
09:41
I honestly don't know in C sharp, in .NET rather. And in fact, the way that Java is only observably immutable is precisely in terms of what we do here. So, we want our observably immutable class to be equatable to each other.
10:01
Obviously, I would normally implement IEquatable of T, but for the purposes of this code, I wanted to keep it short. But we're overriding equals and hashCode. And equals is just going to do the normal checks and then return whether the two names embedded within the observably immutable things are equal to each other.
10:21
Great. A simple implementation of getHashCode. I'm not worrying about nullity here, by the way. Assume that we would check that it's not null. We could just return name.getHashCode from our getHashCode method. But suppose that takes a long time,
10:41
and suppose we happen to know that we'll be using the same observably immutable object in dictionaries. For several dictionaries, it will be the key. And so, getHashCode is going to be called quite often. Well, we know that the name isn't going to change because string is immutable and we haven't provided any way
11:02
of changing which name is in here. So, the hashCode won't change either. So, we could decide to cache it once on construction, compute it then, and then always return it.
11:20
That would be potentially more efficient. And then we can have a genuinely, we could then make this read-only. But we're going to speculatively do it, or lazily do it, rather, so that only when getHashCode is called for the first time will we compute the hashCode.
11:41
And there's a chance. You'll note that we're just assigning cacheHash, and we'll assume that if it's zero, then we haven't calculated it yet. It could be that getHashCode will return zero, and we'll need to go through this computation every time. It's just it's unlikely. We could use a nullable int. It doesn't really matter. The point is, a caller of this class
12:03
is never going to see any changes. If it calls getHashCode several times, even from different threads, it will always get the same answer. When I observe it, it is immutable. However, internally, we know that it's changing.
12:24
Is it immutable? Out of interest, how many of you would describe this as an immutable type? About a third, maybe a quarter. How many of you would describe it as a mutable type?
12:43
A bit fewer. And how many of you haven't put your hand up to either of those? That's fine. I don't know is a perfectly reasonable answer. But this is a good example of immutability isn't as simply defined as you might like it to be.
13:03
In particular, if you start bearing in mind side effects, like it can take a long time to call getHashCode the first time, but not thereafter, maybe that's something we can observe. How long a call to getHashCode takes?
13:21
And that will change between the first and second call. I sometimes read security descriptions of various timing attacks, and they are truly scary, things where you really need to make. I've got to make sure that this takes the same amount of time, however easily I can detect that it's failed.
13:43
If someone can detect that your hundred character password, that you've got the wrong first letter when you guess, well then they can just keep trying until it takes a little bit longer for you to say no. So you always check everything, and then you return no.
14:02
So yeah, in terms of a timing attack, this isn't immutable at all. But I think in most terms I would say it's reasonably immutable. Next question, is this class up here immutable?
14:20
It only has a getter, all its state is immutable. But that doesn't mean that an object assignable to a variable of this type is immutable. So I haven't given an example of this.
14:41
Example of unsealed immutable. So suppose I have a field in here, private read only unsealed semi-immutable foo.
15:04
And I do a constructor which takes one of these, and then just foo equals, this.foo equals foo.
15:22
Right, var naughty equals new derived mutable. And then we can create an example, pass in naughty, and the rest of the code in this class may expect,
15:41
hey this is semi-immutable, that's not going to change on me. But I can then do naughty.otherValue equals 10. And we know, I haven't given enough operations here for this to be a problem,
16:02
but in particular if there were any virtual methods exposed in unsealed semi-immutable which were overridden by derived mutable, which used this other value, then this code that only uses unsealed semi-immutable is in trouble because it could be assuming that it's immutable, but suddenly it's really not.
16:28
Let me give a slight counterexample of this. I said that there's a whole spectrum and it gets a bit weird in some ways. How about this one? We have an unsealed class, but it's got a private constructor.
16:44
And it's unsealed so that I can have two different derived classes which can only exist within the program text of the base class because they've got to have access to the private constructor,
17:01
leaving aside the spec bug that allows you to have a derived, so I'll just show you this because it's really evil, deriving from unsealed private constructor, and initially this will go, you can't compile that.
17:23
That would be trying to have a default constructor that calls a public base class parameterless or tries to call a base class constructor with no arguments, and you can't do that. But we can do public really evil this one,
17:44
public really evil int x this. That should compile in that it's, oh, hey, they fixed it, Bill. For C Sharp 6, they fixed this. The spec says this should compile at the moment, I think,
18:02
but hopefully they've fixed the spec as well. Yeah, you really shouldn't be able to have constructors that call each other, so you never end up having to call a base class constructor, so it's okay. No, this is definitely bad. It would be useful for trying to create your own,
18:23
so here I have the only three instances of this class that ever exist are the ones that I've decided, and I may have other code that depends on that. You can't unless, right, you still can't actually get to it unless,
19:05
so I didn't come up with this code. Someone suggested it along with something to fairly reliably do this, provoke finalization. So if you call this constructor, it will go bang,
19:20
and then in the finalizer it will remember the reference to itself, so we've had something that's never gone through the base class initialization. Goodness knows whether never getting a reference to that object outside the whole constructor chain could be bad in various ways. I'm really glad to see this is prohibited,
19:42
but it would have compiled in Visual Studio 2013, I promise. Yeah, this is a nice pattern to understand that this is something that is effectively sealed as far as the outside world is concerned. It's just that we happen to be able to create derived classes only inside it.
20:04
A variation of this that I use in nodertime, in fact, is to have an abstract class, so it's entirely reasonable to have public abstract class, you can't derive from this.
20:20
Sorry, my fingers are trying to type, you can't touch this. Internal abstract void foo, and the bizarre thing is now if you try to derive from that from another project, it says you're not allowed to do this because you haven't overridden the method that you can't even see.
20:42
It's distinctly strange, but it does mean that in an earlier version of nodertime, I had a calendar system abstract internal class, which I asserted this is effectively immutable because all derived types from it are immutable, and I can guarantee that to you
21:01
because no one else can create any derived types unless they're within the nodertime assembly. Nice pattern to have up your sleeve, but again, not as fully immutable. I haven't even given a fully immutable sealed class that would be the normal thing I would come up with.
21:22
Let me just do this for the sake of next time I give this talk. So if we have something that's really immutable and really obviously immutable,
21:45
if your class is sealed and only contains references to other sealed, other immutable types and value types,
22:08
then I think it's reasonable to say that that's immutable for classes. Whether a struct is immutable or not is a really odd discussion to have
22:21
because it can depend on how you look at things. Even the most immutable-looking struct is sort of not immutable if you poke at it from the outside. If you've got a variable and you replace one value with another value,
22:43
then code that might be running in a different thread in that struct can see its own values change. You could have console.WriteLine this.x, console.WriteLine this.x, and that show different values
23:01
despite nothing in the code ever changing x and x being a private read-only variable. That's confusing, I think. So immutability in structs, yeah, if you try to damage things, then you can wind up in problems.
23:25
And then you can also do really crazy stuff with explicit layouts where you can have two variables that are effectively over the top of each other and if one of them is read-only, hey, I'm changing a read-only variable by changing a different variable.
23:41
So you can get into crazy stuff actually quite easy if you try. But if you're trying to be good, then you should be okay with something like this. Okay, so that's the sort of spectrum of immutability. Why do we want to do this at all?
24:01
How many of you have used Java? Okay, I want you to think of the worst three bits of the standard library and don't tell me them, but was one of them java.util.Date by any chance?
24:23
There are many reasons why java.util.Date is utterly broken, but one of them is it's mutable for something that is a natural value type. And hooray, datetime is a value type in .NET. It's broken in a whole bunch of other different ways, but at least it's a value type.
24:40
We expect a datetime to be in some sense, if I say lowercase primitive, not primitive in the same way that int is, but it should work a bit like int. If I pass it to somewhere, I don't expect it to change. So it makes things easier to work with.
25:02
It's easier to reason about code that is entirely immutable. It's easier to test code because you don't need to worry about so many different situations happening. Well, what happens if I change that and then change the other thing and then call this operation? Well, you can enumerate all the different states that something could get into just by constructing it in that way.
25:29
Immutability can lead to more efficiency. I gave an example this morning where the C Sharp compiler, if you have a lambda expression, let me give an example.
25:42
I should really have a class just for Scratchpad. So if I have a method here and I do action, action equals func of string.
26:08
It doesn't take anything, always returns hello. If I console.writeLine func, so I'm executing the lambda expression or the delegate,
26:23
that's going to print hello. The first time foo is called, the compiler generated code will create an instance of the delegate. After that, it never needs to again because the delegate itself is not going to change.
26:40
There's no state within here that can change. If we use something, if we use a parameter, then we have to create a different delegate every time because we could get different text every time and we could be doing different things in the rest of the code as well.
27:02
But where it can, the C Sharp compiler caches it basically because it knows that the delegate itself will not be changed by anything and it can be reused. We'll see later on how immutability can also do exactly the opposite and cause great inefficiency and lots of copying.
27:25
So as ever with most arguments about best practices and things, you can't say that immutability is definitely more efficient or that mutability is definitely more efficient. It depends on how it's used.
27:42
Immutability generally gets rid of thread safety concerns because a lack of thread safety usually comes when shared state is being used dangerously, where dangerously involves anything changing, anything at all.
28:01
If I have some shared state, so we are all looking at the same stool, it is shared state. If I ask everyone what color it is, everyone will come up with white. It really is white. The light's a bit low, but it really is white.
28:23
If someone can paint the stool, then you need to worry about, well, would everyone see that state change at the same time? And maybe they would, maybe they wouldn't. Who knows? But while it can't change state, you just don't need to worry about it.
28:43
Everyone gets the same answer. And in particular, no one needs to worry about whether the color will change mid-sentence or in the middle of some computations. Let's talk a bit about C-Sharp itself.
29:03
How many of you use C-Sharp 1, sometime before C-Sharp 2? Yeah, all those bad old days before generics. Even so, C-Sharp was still better than Java, so what was it that C-Sharp was meant to be?
29:22
A mixture of C++ and BASIC? C++ and Delphi? Certainly nothing to do with Java, obviously. No, no. No inspiration taken from there at all. But it was already much better than Java. Some of these aspects were already good in Java.
29:43
So Java has a string type which is immutable, observably immutable anyway, and it had string buffer and now has string builder. Java doesn't have value types, or rather, it doesn't have user-defined value types. You only get int, byte, et cetera.
30:05
But this was already a bit better than Java. Back then, just as a reminder, you could not have a property. Let me find an example. Popsicle. Right. So this here, for one thing,
30:24
C-Sharp 1 didn't have automatically implemented properties, so we would have had to do private bool frozen, get return frozen, private set frozen equals value.
30:42
I won't type it all out. But you couldn't even express this because a property just had, well, what's the accessibility of the property? Is it public? Is it private? Here we have a public getter and a private setter. Couldn't do that in C-Sharp 1.
31:00
This is relevant to immutability, partly due to laziness, that until C-Sharp 6, this public getter, private setter is how a lot of people who valued their time over pure perfectionist elegance would write a property that was meant to be immutable.
31:27
Depending on how good I was feeling at the time, I always used to write it as private read-only int value, public int value get return value,
31:43
because then it meant that if I were writing code within the same class, I couldn't accidentally, in fact, no, sorry, even here, I can't assign to value. Whereas if I have public int value 2 get private set,
32:04
it is less code, but I can accidentally set it within the class. And if I haven't written a comment on it properly, maybe a future self or a future maintainer won't even know that I'm not meant to. So it's a cheap way, cheap in terms of lines of code
32:22
and simplicity of implementation of getting a read-only property, but it's not really read-only. Additionally, there are potentially concerns around thread safety and efficiency that having a genuinely read-only field
32:41
is kind of better. Aside from anything else, a read-only field is what I intend. If I can tell the JIT compiler my intentions accurately, then it's more likely that it will be able to do the right thing.
33:00
So C Sharp 1 had a few problems, but a few benefits. C Sharp 2 hardly changed the game on this at all. Obviously, generics, there's lots in C Sharp 2 that is big and good, generics being the biggest thing, but it didn't really move the needle in terms of immutability other than precisely this business about separate public and private get and set.
33:26
Obviously, this isn't the only way that you can do things in C Sharp. You could have a private getter and a public setter. I don't think I've personally used that one. There are variations on this.
33:43
Okay, things happened rather more quickly around C Sharp 3. So in C Sharp 3, basically it was all about link, if you remember, but that involved various things. So I haven't got Lambda expressions in here, although you may want to consider how working with Lambda expressions
34:03
and modifying capture variables fits in with the how easy is it to think about immutability. Coincidentally, a similar feature before Lambda expressions actually hit Java in Java 8, I think it was Java 1.1 introduced anonymous inner classes,
34:21
which were usually used in the same way or for the same purpose that Lambda expressions are in C Sharp. And again, you could use local variables declared in the method that uses the anonymous inner class, but they had to be final. And when the anonymous inner class was constructed,
34:41
the value of that variable was copied into the newly created object. So there are ways in which closures interact with immutability, but I'm not emphasizing them. Anonymous types are really important when it comes to immutability because they are at least shallow immutable.
35:01
So when you use an anonymous type, So if I do var x equals new, name equals John, id equals 23, whatever it is, that has created a new sealed class with a read-only field for name
35:23
and a read-only field for id. It is immutable because I've chosen the right property types. If I picked a new string builder here, it would be shallow immutable with all of the consequences of that.
35:41
Out of interest to any of you VB developers? One, yeah, a few, a few, that's fair. VB's anonymous types are really different. By default, their properties are mutable and don't get involved in equality in hash code for that very reason.
36:05
Here, if I do var y equals new, name equals John, id equals 23, then x dot equals y will return true.
36:20
To make that happen in VB, you have to do whatever the equivalent is. So, new with, I think it's new with, something like that. You can do key, name equals John, key id equals 23. Ignore the syntax itself, but you have to put the key modifier somewhere,
36:42
otherwise they're mutable. I'm really, really, really glad that the C-Sharp team went a different way, but presumably the feeling was that VB developers would be more comfortable with a mutable object in general,
37:01
and that C-Sharp developers would be more comfortable with immutable objects. Talking about link itself, link encourages a more functional viewpoint. There are horrible things you can do with link.
37:21
There's nothing to stop you from writing a Lambda expression that modifies state. I have genuinely seen people write code that's something like var query equals int count equals zero.
37:41
Let's just have a list of int values equals. I'm just going to assign null because I'm never going to run this code, but imagine we had some values. If you want to count how many even numbers we have, you can do values.count x goes to xm1 equals zero.
38:05
That's the way that you should do it. I've genuinely seen people do where x goes to xm1 equals zero, select, x goes to count++, return x, and then say query.toList.
38:40
What's wrong here? Missing brackets. And then print out count. I can tell from the slight gasps that you will recognize that this is a very bad thing to do. This is not what link was designed for.
39:01
I'm also broken here because of x. People are made to feel bad. We as a community do a reasonable job of making people feel bad if they do this. If they persist on doing it after we've made them feel bad, we will make them feel really bad.
39:21
If you happen to do this because you don't know that the bits of the pipeline should be non-side effecting, if you don't know that to start with, then that's fine. But don't keep doing it after people have explained to you why not to do it. We encourage the idea of working with data in an immutable way in link.
39:45
It's not exactly the same as saying we're all functional programmers, but it's a starting point. So that was really good for helping people to think immutable. But at the same time, this list that I just said,
40:01
I'll just use null. I'll use a list of person. I don't know whether I've got a person type. New, and then list person.
40:22
This will have red squiggles everywhere, but name equals John, age equals 39. We encourage people to do this rather than write an immutable person type. So we make it easy to create instances of collections.
40:46
I have slightly less problem with that because it would be nice to have immutable collections, but at least there weren't immutable collections in the standard libraries. But we also encourage people to write mutable types by saying,
41:01
hey, I can initialize it by create the object, then do a bunch of stuff. I'm going to talk a little bit later on about life cycle. Yes, this gets better, as Bill will be showing in more detail and I'll show in some detail.
41:20
This encourages people to think, hey, if I make my class mutable, I can take advantage of all this object initializer stuff. And that's a bit of a shame. At the same time, obviously I use object initializers and this is why Protobuf, my current version, uses mutable types because it does make all of this really easy.
41:43
And that's really readable and that's goodness. But that was all encouraged by C Sharp 3. I've just realized, yeah, I'm falling behind. C Sharp 4 has optional parameters and named arguments.
42:01
Optional parameters are great because it means you can have a long constructor with 15 different parameters and all of them have default values for if you don't need to specify that. If I have an immutable type, then probably, and we'll get into this in a bit, the way of getting data into it is via the constructor.
42:23
Sooner or later, that's the only way of getting data into an immutable type is via a constructor. And if not everything has to be specified, you really need optional parameters to make that work. And if you've got optional parameters, you need named arguments. And even if you want to specify everything,
42:42
the code just looks more readable in many cases if you name your arguments. If you've got two string parameters or worse, the absolute worst thing you can do is have a constructor that takes two boolean parameters and I'm just going to have those parameters
43:01
and then you call it and say, new foo false true. What the hell does that mean? The example I tended to use back when discussing C Sharp 4 was messagebox.show where you could specify a message and you could specify a title.
43:20
And I cannot for the life of me even now remember which one comes first. And the great thing with named arguments is I don't need to remember. I can specify it right there when IntelliSense is saying, hey, here are the parameters, let me remind you of them. Oh, that's great. Now, so that I know what it's going to do later on when I come back to just look at the code,
43:40
I'll name the arguments. So that's all good. C Sharp 5, nothing. Obviously, C Sharp 5 rocks. Asynchrony is great, but nothing for immutability. Fine. C Sharp 6, whoa, big cheers for C Sharp 6. It rocks. Finally, we have read only automatic properties.
44:02
I've only been ranting about that for 12 years or something. As well and less important to me is default values for automatically implemented properties. So back to the scratch pad, I can do, and this works for read only and not read only,
44:21
I can do public int value only have a getter equals 5. And that means that the constructor, maybe I've got some constructors that do assign a value and some of which don't. If it doesn't, then it gets 5. I can have value 2 get set equals 10.
44:41
And that all works. VB people, am I right in saying that VB has always allowed you to give default values for automatically implemented properties? I have a sneaking suspicion. That's something where C Sharp has come from behind, as it were.
45:00
And finally, and this is really odd to mention in terms of immutability, but it sort of goes with the link earlier on. Expression body members. Out of interest, how many of you say expression body what? You all know what they are? There are a few hands, which probably means about twice as many people didn't put their hands up.
45:23
I'll go back to the scratch pad. An expression body member is something that looks a bit like a lambda expression. So I can do a property, value 3, which is a read-only property implemented by doubling value 2.
45:40
And I could do public int addEverything or sum int x, and that goes to value plus x, for example. And you can do it with void ones as well if you really want to print me.
46:07
And this should make no difference whatsoever. It's only missing a return statement and missing braces. But suddenly, and I don't know whether it's because it uses the same syntax as lambda expressions,
46:22
I peppered this through node time liberally, really liberally, to the extent that I counted the return statements in a very crude way, and I think using this got rid of half of the total number of return statements in the node time production assembly. So half of my things were implementable with a single statement.
46:46
You can't use this if you have more than one statement. You've got to use braces. But it suddenly felt like I was being all functional. This is functional code, right? Whereas return value, that's much worse code.
47:03
I don't feel nearly as smart now. This makes no sense. I'm saying it jokingly, but it really does give that impression to you when you're coding. And I think if we get people to think in a functional mindset, even if they're somewhat kidding themselves,
47:20
they will become more properly functional and more likely to want to use immutability. I would add that these are awesome for implementing operators, in particular, operators and equality, and things that can often be expressed in short bits. Okay, so that's C sharp 6.
47:42
I'm not going to do C sharp 7 just yet. Oh, a quarter of an hour. I want to give a few examples of what you can do in C sharp 6 and why they all suck. To give that example, let's think of a data model.
48:02
I'm biased when it comes to all of this because I'm coming from a protobuf background where I'm defining my data model in a .proto file and it defines messages and messages have fields and fields can refer to other messages in obvious ways,
48:21
but the important point is these aren't handwritten types. And handwritten types can have some differences. When you decide to make immutable handwritten types, you don't face quite the same challenges, typically because you don't have quite as rich a data model in terms of your entities. But if you're defining your entities in the same way
48:44
that you would end up putting them in a database, even if it's in some document, NoSQL database, whatever, you may well have things a bit like this. This stretch goal, by the way, I haven't even tried because it's difficult.
49:02
I'm friends with Bill, Bill's friends with me. How can I construct either of us without the other existing? It's difficult. That's why F sharp has let rec, if I remember the book correctly. I haven't written any F sharp, but I think let rec is for I'm going to recursively define this,
49:21
and I can do other things with other things recursively. So we have a person has a name that's just a string, an address which is its own message type, phone numbers which is a collection of them, and each address is just a street and a city.
49:41
That's the easy one. A phone number is also a string which is the number, and a type which is an enum. So in every case, let me just show you, going for builder one, phone number type is the same all the time. That's dead easy. Phone number itself is dead easy as our address or other.
50:03
Address and phone number are both simple in that they both refer to just strings or phone number type, which is itself immutable. So that's fine. Person is where things get more complicated
50:21
because we've got a reference to another message and we've got a collection. So I will whiz through these examples quickly. I haven't yet put this code up on GitHub. I will do if I remember to. If I haven't and you want to look at it, email me. There's no reason why it shouldn't.
50:42
So one very common way, but not so common in .NET, so it's really common outside .NET. Java, it's reasonably common. C++, I believe, of modeling immutability is to have two types. You have a builder type that's mutable and a main type, as it were, that's immutable.
51:04
And here is one way of doing it. It uses the nice accessibility patterns of C Sharp. So I can have a private constructor that is only ever called by my builder class because that's nested.
51:21
So it's as simple as I declare the properties in an obvious way. So if you ignore the code that's in the builder, this is obviously an immutable class. I like code that is obviously immutable. And the builder itself is obviously mutable.
51:42
And when you call build, it just passes itself to the constructor of the declaring type. It could instead, I may or may not have an example, there are so many different variations of this. I've got three different variations, but there are many more.
52:00
We could pass the name and the street and the city straight to address. That ends up getting unwieldy when you have lots and lots of parameters. And particularly if you make public constructors with lots of parameters, and if this is auto-generated code from some other file,
52:24
you need to think about what counts as a backward-compatible change in a proto file. And previously, adding a field is a backward-compatible change. Well, not if you've got a constructor that has non-optional parameters,
52:42
one corresponding to each field. And what if I reorder things? Well, you better not reorder parameters because that could be really evil. If we had a way of saying this is a parameter that can only be specified by a named argument, then reordering wouldn't matter. But that's not a feature that exists in C-Sharp.
53:02
And given how niche it is, it probably never will. But I think it would be quite nice. So that's one builder. When it comes to person, it's slightly more complicated because of the collections and because of the nested message. So again, if we accept that address is immutable,
53:22
this is looking okay. But there are two bits. One is, in our builder, we've got to have a mutable list. And as it happens, I made the choice that it's a read-only property, but it's got a list.
53:41
So anyone can modify this list, but you can't set it to be some other list. There are pros and cons of both ways. So I need to construct an immutable list from that. And this is particularly annoying because here the builder that's meant to be mutable has an immutable type referred to it.
54:07
So when I construct one of these, here's an example. Oh, I should call build at the end, assuming I should really add the builder method.
54:24
Person build, return new person this. It's easy enough to do, but I really need to remember to do it. Go away. Thank you. So now, in order to construct this address, I need to create the new address.builder and then call build.
54:42
And this is all nested within something else that's new person.builder, and I call build at the end. I could make this, instead of taking a builder, I could make the address property of type address.
55:00
I could make it of type address.builder, which would mean that I didn't need to call build over here. But if I ever had an existing address, I couldn't set the address property of a new person that I was building to that. I could potentially have an implicit conversion from one to the other, but that would cause all manner of...
55:21
No, I'm not even going to think about that. I could have set address overloaded, because it's okay to overload setters, but you can't overload a property to accept multiple types. There are many different approaches to this, but they all end up kind of sucking a bit.
55:42
Let me show you very briefly. So that's one where everything is... We always construct a builder that has all the fields and a person that has all the fields, and the builder type has immutable references. Let's look at person in our second example.
56:03
So this is a popsicle immutability thing, where our builder class itself only ever has a single field. It's really cheap to create a builder, because it just has a person underlying it. And when you modify that builder, you modify the person.
56:23
And why is that okay? Because the person is a mixture of two of the examples I gave before. It's popsicle immutable in some cases, and it's also observably immutable. By the time you have a reference to a person, you can't change anything.
56:46
But while we're building it, we modify it in place. The only thing we do need to do is this list of phones. In order for you to be able to mutate it, we need to just cast it in our builder to iList,
57:04
and we need to make sure that it's a list that we can control, and it's this popsicle. I haven't actually implemented this here. It's in the old version of protocol buffers. We say, well, you can keep modifying it until I call build, at which point I'll freeze the list.
57:22
And I would also freeze any... If I had an address builder ever, I would have to build that. Everything mutates until we say build, and we better make sure that we never expose
57:41
the version that we're building until we've finished building it and never mutated after that. In our build method, I have to say, if you want to keep using the builder, that's fine, but we're going to create a new person for you lazily if you ever do.
58:02
In the interest of time, because I have three minutes left, I'm going to skip the third example of the builder pattern, skip straight to withers, which is actually a more common example in C-Sharp than builders,
58:21
where we only have one type, which is good. So this is nice and easy to get your head around. You always know that it's always immutable, no builder mess to deal with. But we have these with methods, and each time we call with, we end up creating a new instance. And the bad thing about this is there's no language support for it,
58:43
if only. If only. So we have to call John with address. And if we were making multiple changes here, we'd have to call with address, with name, with x, with y, at which point we're creating,
59:00
if we're setting n fields, then we're creating n squared memory, because each time we create a whole copy of n values, change one of them, do that n times, we've wasted n squared memory. That's not good.
59:22
So it has various nice things, but it's also a pain to write, frankly, and you end up with an enormous constructor. The future, I think, looks a little like this. So these are record types, hopefully-ish.
59:41
Bill will give you reasons why this is one of the various options being considered. So in our class declaration, we parameterize it as if we were parameterizing a method. And we can specify properties with parameters,
01:00:00
So this is a parameter that's going to end up in a constructor, and it's going to be exposed later as this property name. You can just leave out the colon property name, but at that point you've either got an unconventional parameter name or you've got an unconventional property name, which is a bit of a niggle with me.
01:00:25
And this creates something with one language feature of record types. This means that we have just public constructors, and it's all immutable. I believe we get equals, hash code and toString, just like we did for anonymous classes.
01:00:41
Yay, anonymous types, rather. But where this really comes into its own is with Withers, where we can write, I hope, John with, new contextual keyword, and then we can specify any number of properties that we want to change.
01:01:08
I've typically called these pseudo-mutators, these with calls, but there isn't actually any with method defined in the type. Instead, there's just the constructor, and using the with,
01:01:22
I don't know whether it's going to be called an operator, but the with operator, that just results in a new constructor call to person, filling in John.name, the address we're specifying here, and then John.phones. And if you specify multiple things,
01:01:41
because you want to change multiple things at a time, you only need to make one constructor call. So it's not only readable, it doesn't involve a load of extra code, you know, n methods for n fields. It's also more efficient in terms of how many objects are created. Right, I am officially out of time,
01:02:02
so I will only take another couple of minutes. Oh, so much still to go. I didn't even get to the memory model. Right, very briefly. We are bad at this, because we tend to think of types in terms of inheritance. Object-oriented programming is all about adding functionality.
01:02:21
I take a base type, and my derived type can do everything the base type can do, and something else. That doesn't work when it comes to immutability, because immutability is about taking away the idea of being able to mutate something. And that's why iReadOnlyCollection of T in .NET 4.5,
01:02:44
I think it was introduced, should die in shame. Because many collections implement iReadOnlyCollection, that should be only implemented by ReadOnlyCollections, right? List of T is not a ReadOnlyCollection.
01:03:02
But I think, I have to check, but I think it implements iReadOnlyCollection. This is iReadOnly, because the interface only specifies read-only operations. It says, I can do all these ways of reading things. That's not the same as something being read-only.
01:03:22
It's a negative assertion. It's a contract of not-ness. And it's not just .NET that got this wrong, JodaTime, which is otherwise a great days and time library, in many ways, got this wrong in various ways. This is a simplified form of the type hierarchy.
01:03:41
The real one has about seven levels, because it's crazy. But they have readable foo, in this case, readable instant, and it has subclasses of instant, which is immutable, and mutable instant, which is mutable. Sorry, if I said that instant was mutable, I meant it's immutable. Which means that everything you currently know about,
01:04:03
hey, I will accept the interface type or the abstract base type as my parameter rather than some dirty concrete type. Keep me away from type coupling to concrete data types. No, give me the interface. You can't do that anymore, because you want to know that it's immutable.
01:04:20
So you want to accept this thing and say, I want to make sure that no one derives any further from it. And when you return something from a method, you want to say instant. You don't want to return readable instant, because no one wants a readable instant. Everyone just wants instant. So it screws around with what we've already learnt about OOP.
01:04:46
That was the most important bit of why we fail on this. I've mentioned C Sharp 7 and come to Bill's talk for more on that, but I think there is more to come on this. It may need new runtimes. It may be that .NET is not going to get much, much better at this,
01:05:03
because you really need the library to be built up from the start and the language to be built up from the start and the runtime to be built up, because you want something checking that you get all this right. I showed you various ways that something can be nearly immutable,
01:05:20
but it's not good enough. If you get any leakiness of the mutability, bad stuff happens. So we need better runtime support. We need static analysis. We need things that are fundamentally designed for this, which may end up meaning that we need to rewire ourselves as developers.
01:05:41
But I firmly believe that the benefits of immutability will make that worthwhile. And I suspect if you talk to genuinely hardcore functional programmers, they would argue that in spades. Finally, the one thing that I'm going to be trying to do to make this better, I didn't show the memory model craziness, but there is stuff that you would think is safe
01:06:01
and really, really, really isn't, in theory anyway. The C-sharp memory model is really badly specified at the moment. I'm not working to do this myself, because I don't know anything about this kind of thing. But I'm part of an organisation trying to make it so that...
01:06:22
NDC London in two years' time, maybe I can say, look, we have a shiny memory model for C-sharp. There are still some sharp corners, but at least we can tell you exactly where they are. Right, sorry for running over, and sorry even more so for not being able to get to everything. More will be in the slides that I will put on GitHub.
01:06:41
Do come and ask me if you have any questions. Thank you very much.