. NET provides the struct type. Correct Use can reduce the number of objects, thus reducing GC pressure and improving performance. However, sometimes I will find that some people are aware of this, but sometimes they will not be able to get the corresponding effect if they are negligent and lazy. Here is a real example: Suppose we want to use a pair of int as the dictionary key to map to some data, what do you do? Of course, we can directly use tuple <int, int>, but this may produce a large number of objects. So we plan to use the custom value type:
Private struct mykey {
Private readonly int _;
Private readonly int _ B;
Public mykey (int A, int B ){
_ A =;
_ B = B;
}
}
Is this correct? If you perform a test, you will find that it can be "correctly used", but it is still wrong. The dictionary key is dependent on the gethashcode and equals methods. Because mykey does not provide these two methods, system is automatically used. implementation in valuetype, which causes packing.
Well, let's implement the following:
Private struct mykey {
//...
Public override int gethashcode (){
//...
}
Public override bool equals (object that ){
//...
}
}
What about now? It may be easy to realize now that even if the gethashcode is no longer correct, the equals method will still cause packing because that parameter is still of the object type.
How can this problem be solved? Of course, there is a way, because the collection like hashset <t> or dictionary <tkey, tvalue> does not actually directly call the gethashcode and equals methods, all are called through an iequalitycomparer <t> object:
Public interface iequalitycomparer <in T> {
Bool equals (t x, t y );
Int gethashcode (t obj );
}
If the comparator is not provided when a set is created, the default equalitycomparer <t>. Default object is used. Its constructor is as follows:
Private Static equalitycomparer <t> createcomparer <t> (){
Contract. Ensures (contract. Result <equalitycomparer <t> ()! = NULL );
Runtimetype t = (runtimetype) typeof (t );
// Specialize type byte for performance reasons
If (t = typeof (byte )){
Return (equalitycomparer <t>) (object) (new byteequalitycomparer ());
}
// If t implements iequatable <t> return a genericequalitycomparer <t>
If (typeof (iequatable <t>). isassignablefrom (t )){
Return (equalitycomparer <t>) runtimetypehandle. createinstanceforanothergenericparameter (
(Runtimetype) typeof (genericequalitycomparer <int>), t );
}
// If T is a nullable <u> where u implements iequatable <u> return a nullableequalitycomparer <u>
If (T. isgenerictype & T. getgenerictypedefinition () = typeof (nullable <> )){
Runtimetype u = (runtimetype) T. getgenericarguments () [0];
If (typeof (iequatable <>). makegenerictype (U). isassignablefrom (u )){
Return (equalitycomparer <t>) runtimetypehandle. createinstanceforanothergenericparameter (
(Runtimetype) typeof (nullableequalitycomparer <int>), U );
}
}
// If t is an int-based Enum, return an enum1_itycomparer <t>
// See the method=jit_helpers=unsafe_enum_cast and method=jit_helpers=unsafe_enum_cast_long cases in getilintrinsicimplementation
If (T. isenum & enum. getunderlyingtype (t) = typeof (INT )){
Return (equalitycomparer <t>) runtimetypehandle. createinstanceforanothergenericparameter (
(Runtimetype) typeof (enumequalitycomparer <int>), t );
}
// Otherwise return an objectequalitycomparer <t>
Return new objectequalitycomparer <t> ();
}
It can be seen that different comparator will be used according to different situations. Naturally, the branch that best suits us is the branch that implements the iequatable <t> interface. So we can do this:
Struct mykey: iequatable <mykey> {
//...
Public bool equals (mykey that ){
//...
}
}
This is what eventually meets our requirements.
Coming soon: Almost SNS development and sharing community
It also fails to prevent packing in the end.