📜 ⬆️ ⬇️

Full hiding of fields by properties in C #

At first I thought that it was worth starting an article with a description of the main purpose of properties in C #, but then I realized that with this you can actually “turn around” to a whole article. Therefore, in order not to delay with the introductory part, I will begin immediately with a specific task.

Formulation of the problem


As is known, in the overwhelming majority of cases, properties are used to hide the private or protected field of a class. That is, the properties in this case help to implement the encapsulation of data and methods of working with them. Consider a simple example.
class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  1. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  2. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  3. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  4. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  5. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  6. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  7. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  8. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  9. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  10. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  11. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  12. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  13. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  14. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  15. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
  16. class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .
class Car { const double MINIMAL_SPEED = 0d; const double MAX_KNOWN_CAR_SPEED = 1229.78d; private double maxSpeed; public double MaxSpeed { get { return maxSpeed; } set { if ( value < MINIMAL_SPEED || value > MAX_KNOWN_CAR_SPEED) throw new ArgumentOutOfRangeException( "MaxSpeed" ); maxSpeed = value ; } } } * This source code was highlighted with Source Code Highlighter .

In this example, the Car class is defined, which imposes a restriction on the maxSpeed ​​field so that it has value only in a certain range. This code has a fairly significant problem. Unfortunately, the MaxSpeed ​​property protects the maxSpeed ​​field only from assigning an incorrect speed from outside the Car class. Other methods and properties of the Car class can assign an arbitrary value to the maxSpeed ​​field. Sometimes this is normal, and sometimes it is dangerous. Look at the following (dangerous) factory method in the Car class:
  1. class car
  2. {
  3. // ...
  4. public static Car CreateRandomCar ()
  5. {
  6. return new Car ()
  7. {
  8. maxSpeed ​​= ( new Random ()). NextDouble () * double .MaxValue,
  9. };
  10. }
  11. }
* This source code was highlighted with Source Code Highlighter .

Obviously, this method can create a machine with a maximum speed greater than the value of the MAX_KNOWN_CAR_SPEED constant. Thus, the task is to hide the maxSpeed ​​variable from both the code external to Car and the Car class itself. Let even the class itself accesses this field using the method. And already the method will provide guarantees of the correctness of the assignable value. Sadly, it is impossible to force all project participants to use the MaxSpeed ​​property instead of the maxSpeed ​​field. Yes, and I myself noticed that I forget about this kind of problem fields.

Decision


People practicing labor-OOP programming will say that the task was not worth considering, because its solution is simple - to encapsulate maxSpeed ​​into a separate Speed ​​class and the whole business. However, in my opinion, in some cases this resembles shooting sparrows from a cannon and starting a separate class for each such property is an amateur style. Although, I agree that ideologically this approach is the most pure.
')
My colleague and I found another solution. The solution has its drawbacks and some limitations. So, below is a class that makes it easy to describe properties that encapsulate such “problem” fields:
  1. public class HidingProperty <T>
  2. {
  3. public delegate T1 Getter <T1> ( ref T1 currentValue);
  4. public delegate void Setter <T2> ( ref T2 currentValue, T2 newValue);
  5. private T _storedValue;
  6. private Getter <T> _getter;
  7. private Setter <T> _setter;
  8. public HidingProperty (Getter <T> getter, Setter <T> setter)
  9. : this ( default (T), getter, setter) {}
  10. public HidingProperty (T initialValue, Getter <T> getter, Setter <T> setter)
  11. {
  12. _storedValue = initialValue;
  13. _getter = getter;
  14. _setter = setter;
  15. }
  16. public void Set (T newValue)
  17. {
  18. _setter ( ref _storedValue, newValue);
  19. }
  20. public T Get ()
  21. {
  22. return _getter ( ref _storedValue);
  23. }
  24. }
* This source code was highlighted with Source Code Highlighter .

Here is an example of the problem field description and properties:
  1. private HidingProperty < double > NewMaxSpeed ​​= new HidingProperty < double > (
  2. ( ref double currentValue) => { return currentValue; },
  3. ( ref double currentValue, double newValue) =>
  4. {
  5. if (newValue <MINIMAL_SPEED || newValue> MAX_KNOWN_CAR_SPEED)
  6. throw new ArgumentOutOfRangeException ( "NewMaxSpeed" );
  7. currentValue = newValue;
  8. }
  9. );
* This source code was highlighted with Source Code Highlighter .

You will have to work with the new field using the Get () and Set () methods:
var currentSpeed ​​= mazda.NewMaxSpeed.Get ()
mazda.NewMaxSpeed.Set (currentSpeed ​​+ 10d);

Yes, colleagues. This, of course, is not a super sexy code. An example of working with a field reminds me of programming in Java. However, the main problem is solved, with relatively little blood. Now, when accessing NewMaxSpeed ​​both from outside the Car and inside it, the same check will be performed for the occurrence of a new value in the specified range.

Disadvantages, limitations, side effects


I confess that I would like to see the possibility of completely hiding the field by a property in the language itself. My colleague sees for himself something like the syntax for this business:
  1. public double Speed
  2. {
  3. double speed;
  4. get { // getter code }
  5. set { // Setter Code }
  6. }
* This source code was highlighted with Source Code Highlighter .

The same variant that was given by me makes it necessary to work with the field through nasty Get, Set methods, not allowing direct assignments. I did not manage to overcome this. If we describe an implicit casts operator that allows writing double x = car.MaxSpeed; quite simple, here it is to realize the possibility of using car. MaxSpeed ​​= 10d; turned out to be impossible.

Another nasty thing associated with the proposed solution is the impossibility of specifying different access modifiers for the getter and setter, which, of course, is a big disadvantage.

I did not even consider the issue of serialization of such fields / properties. I guess there may be difficulties. Although I do not rule out that simply “hanging up” the Serializable attribute on the HidingProperty class may be enough.

findings


So far it is difficult for me to say how often within our project my colleague and I will refer to this decision. However, I am not very picky about the style of the code and I will apply the class in homework.

Thanks for attention! I would appreciate criticism of the decision and new thoughts.

Source: https://habr.com/ru/post/116234/


All Articles