Yesterday. (1) I mentioned that if we further construct the implementation in this article and extract an abstract class as the base class of the custom Enumeration type, it will certainly be helpful for subsequent development. Today, let's continue to explore it!
I have mentioned in the first set that this method of using classes or structures to replace enumeration types cannot replace all built-in enumeration values, but can be used as necessary. Be careful with your friends!
This solution seems to be refactored. However, in the previous set, we should note that many operators are rewritten in the Code. Operators are static members, but static members cannot be inherited. helper members such as parse are also static, this means that it is impossible to completely reconstruct a class containing the necessary static members in an abstract class. In this case, we cannot directly control the constraints from the code. We can only verbally (or in the document) agree on how to do it.
That is to say,Chen was excited and made a serious mistake in his mindset.If you want to refactor, you can only partially refactor it, which is not powerful enough. Now, let's take a deeper look at this solution and look at its interests.
First, let's look at the most streamlined mode of this solution:
1 [serializable] // not necessary
2 Public sealed class useroperates
3 {
4 public static readonly useroperates none = new useroperates (0 );
5 public static readonly useroperates READ = new useroperates (1 );
6 public static readonly useroperates write = new useroperates (2 );
7 public static readonly useroperates Delete = new useroperates (4 );
8
9 private useroperates (INT value) {This. value = value ;}
10
11 Public int value {Get; private set ;}
12}
There are several elements:
- The enumerated value field must be read-only;
- The constructor must not be changed from the outside, so it is marked as private;
- The class is declared as sealed, and its polymorphism is limited;
- To obtain the original values of the enumerated value field from the outside, we define a value member. From this perspective, this example is not the most streamlined;
Modify this class and then look at it:
1 [serializable] // not necessary
2 public class useroperates
3 {
4 public static readonly useroperates none = new useroperates (0 );
5 public static readonly useroperates READ = new useroperates (1 );
6 public static readonly useroperates write = new useroperates (2 );
7 public static readonly useroperates Delete = new useroperates (4 );
8
9 public static readonly list <useroperates> enumfields = new list <useroperates> {
10 none,
11 read,
12 write,
13 };
14
15 protected useroperates (INT value) {This. value = value ;}
16
17 Public int value {Get; private set ;}
18
19 public static useroperates operator | (useroperates P1, useroperates P2) {return parse (p1.value | p2.value );}
20
21 public static useroperates operator ^ (useroperates P1, useroperates P2) {return parse (p1.value ^ p2.value );}
22
23 public static useroperates operator & (useroperates P1, useroperates P2) {return parse (p1.value & p2.value );}
24
25 public bool hasflag (useroperates flag) {return (this. Value & flag. Value) = flag. value ;}
26
27 public static useroperates parse (INT value)
28 {
29 // Note: Bit domain operations are not considered here
30 return enumfields. firstordefault (item => item. value = value )?? None;
31}
32}
We have added some operators, but let's look at the following code. What do you think they output separately?
1 var o1 = UserOperates.Write | UserOperates.Delete;
2 var o2 = UserOperates.Read | UserOperates.Write | UserOperates.Delete;
3 var o3 = (UserOperates.Read | UserOperates.Write | UserOperates.Delete) ^ UserOperates.Read;
4 var o4 = (UserOperates.Read | UserOperates.Write | UserOperates.Delete) & UserOperates.Read;
5 var o5 = (UserOperates.Read | UserOperates.Write | UserOperates.Delete).HasFlag(UserOperates.Write);
6
7 Trace.WriteLine(o1.Value);
8 Trace.WriteLine(o2.Value);
9 Trace.WriteLine(o3.Value);
10 Trace.WriteLine(o4.Value);
11 Trace.WriteLine(o5);
The answer is:
1 0
2 0
3 2
4 0
5 False
None of them are correct!How can this happen?
This is a bug in our code. After we calculate the original values of each enumerated value separately, we try to find the matched items in the dictionary, and the results will not be found, therefore, none is returned as the default field, and its value is 0. One of the solutions:
1 public static readonly UserOperates None = new UserOperates(0);
2 public static readonly UserOperates Read = new UserOperates(1);
3 public static readonly UserOperates Write = new UserOperates(2);
4 public static readonly UserOperates ReadWrite = new UserOperates(3);
5 public static readonly UserOperates Delete = new UserOperates(4);
6 public static readonly UserOperates ReadDelete = new UserOperates(5);
7 public static readonly UserOperates WriteDelete = new UserOperates(6);
8 public static readonly UserOperates ReadWriteDelete = new UserOperates(7);
9
10 public static readonly List<UserOperates> EnumFields = new List<UserOperates> {
11 None,
12 Read,
13 Write,
14 ReadWrite,
15 Delete,
16 ReadDelete,
17 WriteDelete,
18 ReadWriteDelete
19 };
Run again:
1 6
2 7
3 5
4 1
5 True
The output is completely correct! In fact, this is a defect in "using classes to replace some enumeration values". We have to list any possible combinations to ensure normal operation. In view of this fact,Old Chen's suggestion:Do not use this solution for enumeration types that require bitfield operations, especially those more complex bitfield enumeration types, which may increase our coding burden!
Another potential bug is that the value of the enumfields field can be changed externally. It also needs to be avoided. Simply change its modifier public to protected.
In addition, we also found that the tostring () method was written incorrectly yesterday and should be modified:
1 public override string ToString() { return this.Name; }
There are other difficulties encountered throughout the refactoring process. For example, operators cannot use generics. This means that if you want to use polymorphism to refactor this code, operators must be rewritten in each class, it also involves various types of conversions, and the details are very complicated.
All in all, the dream of using abstract class refactoring mentioned yesterday has basically been shattered, because this refactoring brings more pain than joy, so it is better not to refactor. This alternative Enumeration type solution is very useful and should not be abused!
For myself, I should be more cautious when writing articles in the future. After all tests have been done, I will show myself up again, and I will be able to get my own food if I am arrogant! Thank you for your continued criticism and guidance!
Forecast: the third set will be completed tomorrow. We will discuss how to use the attribute feature to bind multiple constants of enumeration values. At the same time, we will release two Enumeration type encapsulation classes that I have used.