> YMMV as you say, but I think that your needs are very specific if that's your base principle.
Not really. This is for a very large web-based medical practice-management system and tertiary services. It mostly sits on top of stripped-back ASP.NET, but yes it's a framework I developed myself because 9 years ago when this project started ASP.NET was awful.
So yeah, whilst it's non-standard, it's not rocket science, or some bizarre parallel C# universe. I still need to get data from the server to the client in an efficient way, I still have controllers, server pages, database CRUD, etc.
> Objects in C# are mutable by design
Objects are mutable by default, but there also exists a keyword called readonly which has been there since v1.0 of C#, so I would argue that it's just as much of a language feature. It not being the default doesn't make it any less of a valid approach. One of the most used library types is immutable: String.
> It sounds like your approach is closer to functional programming (but again, I don't know the context)
I try to follow that route, yes. Clearly it's not always possible because of the limitations of the language or the .NET framework, and that's fine, I'm not going to be dogmatic over it. I think however that immutability where possible helps code quality overall.
That's very interesting, but none of those are really issues for me, and I'd argue they'd be minor issues for most people too:
> You lose binary compatibility
We always compile from source
> You can use a field for ref parameters, whereas you can't (in C# at least) use a property in the same way.
Ref fields are nasty, I wouldn't want to see anyone on my team using them. obviously passing a readonly field to a ref is pretty pointless. So non-issue for me.
> Involving a mutable structs
Don't use mutable structs.
> You lose reflection compatibility
Call GetFields instead of GetProperties. Non-issue internally. I guess if a 3rd party lib didn't support fields then it might be a problem, so far it hasn't been.
> Achievable with properties (with private setter), so I wouldn't count it as a benefit.
No it isn't, because the method you called could call a method on the object you passed, affecting the underlying state of the object. If you work on small code-bases and you understand the implications of every function you call then great, but the added peace of mind knowing that this structure you passed to a method isn't going to be changed (now or by a programmer in the future) is very powerful.
It's also makes method signatures tell you explicitly the behaviour of the code within, which makes it easier to look at the surface of a library to know what it does, i.e.:
MutableState state = new MutableState(...);
MutableLib.DoSomething(state);
// ... any code run after DoSomething is now flying-blind, has state changed?
public class MutableLib
{
public static void DoSomething(MutableState state)
{
// What happens here? Does MutableState get changed? How can you know when the
// method returns 'void' and takes a mutable object? You have to look at the code
// to know for sure.
}
}
Then the immutable version. Notice how the signature to DoSomething is explicit in its purpose.
ImmutableState state = new ImmutableState(...);
var newState = ImmutableLib.DoSomething(state);
// ... any code run after DoSomething is entirely aware that state has changed
public static class ImmutableLib
{
public static ImmutableState DoSomething(ImmutableState state)
{
// What happens here? Well it doesn't matter to the caller, they know
// they get a new ImmutableState back, and if they chose to ignore it
// then the original state object they passed in will be unaltered.
}
}
It also allows for free transactional behaviour with rollbacks, which can be super useful:
ImmutableState state = new ImmutableState(...);
try
{
var newState = ImmutableLib.DoSomething(state);
newState = ImmutableLib.DoSomethingElse(newState);
newState = ImmutableLib.DoAnotherThing(newState);
// Commit
state = newState;
}
catch
{
// If an exception fires then state won't have changed.
}
> I see where you're coming from, but to me you are pointing out the advantages of immutability, not choosing fields over properties.
You're right that I'm primarily arguing for immutability, but I would also argue that properties can't achieve immutability because the object can always change itself. A private property can always be changed by a member function. Clearly you can make classes that are immutable for all intents and purposes by using properties; I just prefer the explicit nature of 'readonly' - it's a language feature to be used in my humble opinion.
There's also the argument that properties are an extra unnecessary abstraction for this case. With a readonly field/property it is only set once, so having a layer of getter/setter logic is unnecessary. You only need that logic once at construction.
> What happens here? Well it doesn't matter to the caller, they know they get a new ImmutableState back, and if they chose to ignore it then the original state object they passed in will be unaltered.
Yes if they choose, but how are they supposed to make this choice? :)
You can't make an informed decision about it UNLESS you know what is the difference between ImmutableState instance returned by DoSomething and the original one.
We're back to the same problem, then. We can't be sure what DoSomething does without peeking into its implementation.
That it produces another instance in process may be nice, but the core problem is the same.
Can I know for sure how the state of my MutableState object changed?
Can I know for sure what is the difference between the ImmutableState I passed to you and the other ImmutableState you made me pull out of your blackbox? I'm flying blind just the same
> It also allows for free transactional behaviour with rollbacks, which can be super useful:
Agreed, but there is more than one right way to achieve it. This could be also done with mutable objects and deep copies. Just make yourself a backup clone before you go wild.
The cost of your approach is that all behaviour and logic is moved into some ImmutableLib class, so you're creating various "Libs", "Managers", "Services" and the like, which means that you're essentially writing procedural code.
This OOP anti-pattern is known as anemic domain model.
Thanks for the link to Rich Kickey's presentation, it seems to be some food for thought. I'll watch it when I have some free time on my hands and maybe it will affect the way I see it.
Not really. This is for a very large web-based medical practice-management system and tertiary services. It mostly sits on top of stripped-back ASP.NET, but yes it's a framework I developed myself because 9 years ago when this project started ASP.NET was awful.
So yeah, whilst it's non-standard, it's not rocket science, or some bizarre parallel C# universe. I still need to get data from the server to the client in an efficient way, I still have controllers, server pages, database CRUD, etc.
> Objects in C# are mutable by design
Objects are mutable by default, but there also exists a keyword called readonly which has been there since v1.0 of C#, so I would argue that it's just as much of a language feature. It not being the default doesn't make it any less of a valid approach. One of the most used library types is immutable: String.
> It sounds like your approach is closer to functional programming (but again, I don't know the context)
I try to follow that route, yes. Clearly it's not always possible because of the limitations of the language or the .NET framework, and that's fine, I'm not going to be dogmatic over it. I think however that immutability where possible helps code quality overall.
> Fields an properties are incompatible? How? > See http://csharpindepth.com/articles/chapter8/propertiesmatter..... - "Compatibility issues".
That's very interesting, but none of those are really issues for me, and I'd argue they'd be minor issues for most people too:
> You lose binary compatibility
We always compile from source
> You can use a field for ref parameters, whereas you can't (in C# at least) use a property in the same way.
Ref fields are nasty, I wouldn't want to see anyone on my team using them. obviously passing a readonly field to a ref is pretty pointless. So non-issue for me.
> Involving a mutable structs
Don't use mutable structs.
> You lose reflection compatibility
Call GetFields instead of GetProperties. Non-issue internally. I guess if a 3rd party lib didn't support fields then it might be a problem, so far it hasn't been.
> Achievable with properties (with private setter), so I wouldn't count it as a benefit.
No it isn't, because the method you called could call a method on the object you passed, affecting the underlying state of the object. If you work on small code-bases and you understand the implications of every function you call then great, but the added peace of mind knowing that this structure you passed to a method isn't going to be changed (now or by a programmer in the future) is very powerful.
It's also makes method signatures tell you explicitly the behaviour of the code within, which makes it easier to look at the surface of a library to know what it does, i.e.:
Then the immutable version. Notice how the signature to DoSomething is explicit in its purpose. It also allows for free transactional behaviour with rollbacks, which can be super useful: > I see where you're coming from, but to me you are pointing out the advantages of immutability, not choosing fields over properties.You're right that I'm primarily arguing for immutability, but I would also argue that properties can't achieve immutability because the object can always change itself. A private property can always be changed by a member function. Clearly you can make classes that are immutable for all intents and purposes by using properties; I just prefer the explicit nature of 'readonly' - it's a language feature to be used in my humble opinion.
There's also the argument that properties are an extra unnecessary abstraction for this case. With a readonly field/property it is only set once, so having a layer of getter/setter logic is unnecessary. You only need that logic once at construction.