0

As properties using custom logic (aka any computed property in c#) need a backing field (source one, two), it can occur that you assign to that backing field directly, changing it's value without applying it's setter logic.

You could of course encapsulate this value into some subclass, and then set the accessibility so that this behavior is no longer possible, but I'm looking for a workaround that does not require any further encapsulation, as encapsulating a singular value feels.. silly.

Say I have this class with a value which should always be clamped between 1 and positive infinity, as it may otherwise cause unexpected behavior (some multiplier with a minimum value of 1 for example).

My current workaround is using naming conventions:

public class Foo
{
    private float _someVar;
    private float b_someMultiplier; 
    private float _SomeMultiplier
    {
        get { return b_someMultiplier; }
        set { b_someMultiplier = Mathf.Clamp(value, 1, float.PositiveInfinity); }
    }
    public float SomeVar
    {
       get { return _someVar; }
    }
}
  1. I prefix my privates with _ as by the conventions
  2. I name my properties and publics using PascalCase.
  3. To differentiate between the backing field (which should never be used directly), I mark it with the prefix b_.

However, this still makes b_someMultiplier directly accessible in the Foo class internally.

Is there some more 'surefire' way of preventing developers to accidentally alter that backing field b_someMultiplier and skipping the setter logic?

My current way of doing it using naming conventions feels more like a botch than a solution: I'd have to explain this to anyone I send my code to.

KamielDev
  • 481
  • 3
  • 13
  • 1
    There's a new feature coming in the next C# version (C# 12): [Proposal: Semi-Auto-Properties; field keyword](https://github.com/dotnet/csharplang/issues/140). It will allow you to write `float MyProperty { get; set => field = MathF.Clamp(value, 1, float.PositiveInfinity); }` without declaring a backing field. If a read-only property is an option, then you can replace the `set` keyword by the [`init`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/init) keyword allowing you to initialize the property only in the constructor or in an object initializer. – Olivier Jacot-Descombes Dec 22 '22 at 15:36
  • 2
    There's a problem in your code. In the property setter `set { Mathf.Clamp(value, 1, float.PositiveInfinity); }`, you're never assigning the Clamped value to the backing field. It's missing `b_someMultiplier = ...` at the front. And any mechanism restrticting direct access to the backing field would also have to apply to the fixed setter, so... – Orion Dec 22 '22 at 15:52
  • @Orion You are right, I missed that as I wrote this from memory. I will edit the question to reflect the correct code. – KamielDev Dec 22 '22 at 15:54
  • @OlivierJacot-Descombes Interesting. Thanks! Can I conclude that there are, as of right now, no other ways of working around this issue other than resorting to either custom coding conventions as in my question or custom linting/compiler rules? – KamielDev Dec 22 '22 at 15:56
  • I see no other way, except that you could use a nested private or protected struct for encapsulation instead of a class. This would be be more lightweight as it would not require you to create an instance. – Olivier Jacot-Descombes Dec 22 '22 at 17:07

1 Answers1

0

You can move it out to another class Bar and have an instance of that in Foo, where you put your logic into that Bar setter for that property.

Mocas
  • 1,403
  • 13
  • 20
  • 1
    That would require further encapsulation, which my question is explicitly trying to avoid. – KamielDev Dec 22 '22 at 15:47
  • Sorry but if your property has a criteria for what values it accepts then you have to have a validating logic somewhere guarding the property. If the same class can't fully protect the validation then your only way is to put it somewhere else. What is the issue with that? – Mocas Dec 22 '22 at 15:55
  • By the way, the solution doesn't add further encapsulation, it just moves it somewhere else. – Mocas Dec 22 '22 at 15:56
  • I may have phrased that incorrectly, but I'm just interested in a solution that does not require another class by default. What you state would certainly be possible, it's just not scratching my itch. – KamielDev Dec 22 '22 at 16:00