Reputation: 13297
In my current project, I need to throw a lot of data around with a good understanding of what is a copy and what is a reference, in particular — implement a lot of deep cloning. Thankfully, a lot of my data is contained in simple structs, so I can just assign them or pass them as parameters and be sure that they aren't referencing each other.
However, I realized that the code that is doing that is based on assumption that these types are struct, and that if anyone is going to change their type to a class some time down the road, they may not notice errors right away, and when they do, it will be a hell to debug.
In general, I consider code which correctness is based on assumption that is not explicitly stated in the same file to be a smell, and especially when this assumption can be changed without immediately breaking the build. So, this situation really gives a bad smell for me.
Is what I'm doing really a bad practice? Have you experienced any problems that I described in real codebases, or am I imagining things? And if you have, what would be a good way to make things more explicit?
Here's an example of what I'm talking about (this is my actual code, with all names changed and other functionality stripped):
public class Example
{
public readonly DataOfType;
public readonly DataOfOtherType;
public Example(Type _DataOfType, OtherType _DataOfOtherType)
{
DataOfType = _DataOfType;
DataOfOtherType = _DataOfOtherType;
}
public Example DeepClone()
{
return new Example(DataOfType, DataOfOtherType);
}
}
The DeepClone
method is correct because Type
is an enum and OtherType
is a struct, but you would never know it from this code.
Upvotes: 2
Views: 121
Reputation: 700472
Instead of thinking of data in terms of references and copies, I think that you should think about it in terms of mutability.
If you have an immutable type, then it doesn't matter if it is a struct or a class, you can treat it the same becase you know that it can't change. You can safely pass it around without caring about whether it's a reference or a copy, that becomes just an implementation detail.
Whenever you need to change a value, the immutability will force you to create a copy. That way you can never mess up a value in one place that is used in another place.
The String
class is an example of how this is used. As strings are immutable you can pass them around as references without the risk of a string ever changing. All the methods in the String
class that makes changes to a string will create a new string with the result.
Upvotes: 3
Reputation: 157018
If you make the struct
immutable, which it should be in my opinion, you won't have issues with references being spread once the struct
becomes a class.
Besides that, I think you have to help the 'maintainer' and 'user' of the struct
to understand the implications.
You can easily do that with a remarks
section in your Visual Studio code documentation. This will pop up on every one referencing the type.
I wouldn't make too much changes to the code to show the data type is a struct
or do some type checking. This may only lead to confusion.
Upvotes: 1
Reputation: 239734
You could create a generic constrained to only allow structs, but uncreatable, and then create variables of that type at the top of each method where the assumption is required.
E.g.:
public class AssumeStruct<TStruct> where TStruct : struct
{
private AssumeStruct(){}
}
And then:
public void MethodAssumingStructSemantics(S1 arg1)
{
AssumeStruct<S1> t = null;
...
}
This should then cause a compiler error if the nature of the S1
type is changed later.
Upvotes: 3