Objective C # principle 26: Use icomparable and icomparer to implement the sequential relationship between objects
Item 26: Implement ordering relations with icomparable and icomparer
Your type should have an ordered relationship to describe how they are stored and sorted in the collection .. The Net Framework provides two interfaces for you to describe the ordered relationship of objects: icomparable and icomparer. Icomparable defines the natural sequence for your class, and the class implementing the icomparer interface can describe other optional sequence. You can define and implement your own Relational operators (<,>,<=, >=) when implementing interfaces to avoid inefficient comparison of links by default during runtime. This principle will discuss how to implement sequential relationships so that the core of the. NET Framework can sort your types through the interfaces you define. In this way, you can get better efficiency in some operations.
The icomparable interface has only one method: compareto (), which follows the implementation principle of the strcmp function in the traditional C function library: if the current object is smaller than the target object, its return value is less than 0; if they are equal, 0 is returned. If the current object is larger than the target object, the return value is greater than 0. Icomparable uses system. object as the parameter. Therefore, when using this function, you need to check the runtime objects. During each comparison, you must re-interpret the parameter type:
Public struct Customer: icomparable
{
Private readonly string _ name;
Public customer (string name)
{
_ Name = Name;
}
# Region icomparable members
Public int compareto (object right)
{
If (! (Right is customer ))
Throw new argumentexception ("argument not a customer ",
"Right ");
Customer rightcustomer = (customer) right;
Return _ name. compareto (rightcustomer. _ name );
}
# Endregion
}
There are many differences between implementation comparison and icomparable interface consistency. First, you need to check the parameter runtime type. IncorrectCodeYou can use any type as a parameter to call the compareto method. In addition, the correct parameters must be packed and disassembled before the actual comparison can be provided. This additional overhead is required for each comparison. When sorting sets, the average number of comparisons on objects is N x log (n), and three times of packing and unpacking are generated each time. For an array with 1000 points, this will generate about 20000 packing and unpacking operations. The average calculation is: N x log (n) has 7000 times, three times of packing and unpacking are performed for each comparison. Therefore, you must find an optional comparison method. You cannot change the definition of icomparable. compareto (), but this does not mean that you have to make your users suffer performance loss in a weak implementation. You can reload the compareto () method so that it can only operate on the customer object:
Public struct Customer: icomparable
{
Private string _ name;
Public customer (string name)
{
_ Name = Name;
}
# Region icomparable members
// Icomparable. compareto ()
// This is not type safe. The runtime type
// Of the right parameter must be checked.
Int icomparable. compareto (object right)
{
If (! (Right is customer ))
Throw new argumentexception ("argument not a customer ",
"Right ");
Customer rightcustomer = (customer) right;
Return compareto (rightcustomer );
}
// Type-safe compareto.
// Right is a customer, or derived from customer.
Public int compareto (customer right)
{
Return _ name. compareto (right. _ name );
}
# Endregion
}
Now, icomparable. compareto () is an implicit interface implementation. It can only be called through the reference of the icomparable interface. Your users can only use one type of secure calls, and insecure comparisons are not accessible. The following unintentional errors cannot be compiled:
Customer C1;
Employee E1;
If (c1.compareto (E1)> 0)
Console. writeline ("customer one is greater ");
This cannot be compiled because for public customers. the compareto (customer right) method does not match the parameter while the icomparable. the compareto (object right) method is not accessible. Therefore, you can only access it by forcibly converting it to the icomparable interface:
Customer C1;
Employee E1;
If (C1 as icomparable). compareto (E1)> 0)
Console. writeline ("customer one is greater ");
When you implement the icomparable interface implicitly and provide a type-safe comparison, the strong type comparison of the overloaded version increases the performance and reduces the possibility of others misuse the compareto method. You cannot see it yet. all the advantages of the sort function in the. NET framework. This is because it still uses the interface pointer (see Principle 19) to access the compareto () method, but when the types of two objects have been passed, the code performance will be better.
We will make a small modification to the customer structure. The C # language can overload standard Relational operators. These operators should use the type-safe compareto () method:
Public struct Customer: icomparable
{
Private string _ name;
Public customer (string name)
{
_ Name = Name;
}
# Region icomparable members
// Icomparable. compareto ()
// This is not type safe. The runtime type
// Of the right parameter must be checked.
Int icomparable. compareto (object right)
{
If (! (Right is customer ))
Throw new argumentexception ("argument not a customer ",
"Right ");
Customer rightcustomer = (customer) right;
Return compareto (rightcustomer );
}
// Type-safe compareto.
// Right is a customer, or derived from customer.
Public int compareto (customer right)
{
Return _ name. compareto (right. _ name );
}
// Relational operators.
Public static bool operator <(customer left,
customer right)
{< br> return left. compareto (right) <0;
}< br> Public static bool operator <= (customer left,
customer right)
{< br> return left. compareto (right) <= 0;
}< br> Public static bool operator> (customer left,
customer right)
{< br> return left. compareto (right)> 0;
}< br> Public static bool operator >=( customer left,
customer right)
{< br> return left. compareto (right) >=0;
}< BR ># endregion
}
The sequential relationship of all customers is as follows: sort by name. Soon, you may want to create a report, which is sorted by the customer's revenue. You still need the general comparison mechanism defined in the Custom structure: sort by name. You can add a class that implements the icomparer interface to meet this new requirement. Icomparer provides another standard choice for type comparison. In. Net FCL, any function that works on the icomparable interface provides an overload to sort objects through the interface. Because you are the author of the customer structure, you can create a new class (revenuecomparer) as a private nested class of the customer structure. It is exposed to users through static attributes of the customer structure:
Public struct Customer: icomparable
{
Private string _ name;
Private double _ revenue;
// Code from earlier example elided.
Private Static revenuecomparer _ revcomp = NULL;
// Return an object that implements icomparer
// Use lazy evaluation to create just one.
Public static icomparer revenuecompare
{
Get
{
If (_ revcomp = NULL)
_ Revcomp = new revenuecomparer ();
Return _ revcomp;
}
}
// Class to compare MERs by revenue.
// This is always used via the interface pointer,
// So only provide the interface override.
Private class revenuecomparer: icomparer
{
# Region icomparer members
Int icomparer. Compare (object left, object right)
{
If (! (Left is customer ))
Throw new argumentexception (
"Argument is not a customer ",
"Left ");
If (! (Right is customer ))
Throw new argumentexception (
"Argument is not a customer ",
"Right ");
Customer leftcustomer = (customer) left;
Customer rightcustomer = (customer) right;
Return leftcustomer. _ revenue. compareto (
Rightcustomer. _ revenue );
}
# Endregion
}
}
In the final version, the customer structure contains the revenuecomparer class, so that you can sort the objects by name in the natural order. You can also use this method to expose the objects, class that implements the icomparer interface to sort customers by revenue. If you cannot accessSource codeYou can also provide an icomparer interface to sort any of its public attributes. This habit is used only when you cannot obtain the source code. It is also used when a class in the. NET Framework requires different sorting bases.
This principle does not involve the equals () method and the = Operator (see Principle 9 ). Sorting and equality are clear operations. You do not need to implement an equal comparison to express the sorting relationship. In fact, the reference type is usually sorted Based on the object content, and equal is based on the Object ID. When equals () returns false, compareto () returns 0. This is completely legal, and equality and sorting are completely unnecessary.
Note that the objects discussed here are two operations: sorting and equality, not specific objects. For some special objects, equality may be related to sorting .)
the icomparable and icomparer interfaces provide a standard mechanism for sorting types. icomparable should be used in most natural sorting. When you implement the icomparable interface, you should reload consistent comparison operators for sort types (<,>,<=, >= ). Icomparable. compareto () uses system. object as the parameter. You also need to reload a type-safe compareto () method. Icomparer can provide an optional sort basis for sorting, which can be used for some types that do not provide you with a Sort basis and provide your own sort basis.
==========< br>
item 26: Implement ordering relations with icomparable and icomparer
your types need ordering relationships to describe how collections shold be sorted and searched. the. net Framework defines two interfaces that describe ordering relationships in your types: icomparable and icomparer. icomparable defines the natural order for your types. A type implements icomparer to describe alternative orderings. you can define your own implementations of the Relational operators (<, >,<=, >=) to provide type-specific comparisons, to avoid some runtime inefficiencies in the interface implementations. this item discusses how to implement ordering relations so that the core. NET framework orders your types through the defined interfaces and so that other users get the best performance from these operations.
The icomparable Interface contains one method: compareto (). this method follows the long-standing tradition started with the C library function strcmp: its return value is less than 0 if the current object is less than the comparison object, 0 if they are equal, and greater than 0 if the current object is greater than the comparison object. icomparable takes parameters of type system. object. you need to perform runtime type checking on the argument to this function. every time comparisons are saved med, you must reinterpret the type of the argument:
Public struct Customer: icomparable
{
Private readonly string _ name;
Public customer (string name)
{
_ Name = Name;
}
# Region icomparable members
Public int compareto (object right)
{
If (! (Right is customer ))
Throw new argumentexception ("argument not a customer ",
"Right ");
Customer rightcustomer = (customer) right;
Return _ name. compareto (rightcustomer. _ name );
}
# Endregion
}
There's a lot to dislike about implementing comparisons consistent with the icomparable interface. you 've got to check the runtime type of the argument. incorrect code cocould legally call this method with anything as the argument to the compareto method. more so, proper arguments must be boxed and unboxed to provide the actual comparison. that's an extra runtime expense for each compare. sorting a collection will make, on average N x log (n) comparisons of your object using the icomparable. compare method. each of those will cause three boxing and unboxing operations. for an array with 1,000 points, that will be more than 20,000 boxing and unboxing operations, on average: N x log (n) is almost 7,000, and there are 3 box and Unbox operations per comparison. you must look for better alternatives. you can't change the definition of icomparable. compareto (). but that doesn't mean you're forced to live with the performance costs of a weakly typed implementation for all your users. you can create your own override of the compareto method that expects a customer object:
Public struct Customer: icomparable
{
Private string _ name;
Public customer (string name)
{
_ Name = Name;
}
# Region icomparable members
// Icomparable. compareto ()
// This is not type safe. The runtime type
// Of the right parameter must be checked.
Int icomparable. compareto (object right)
{
If (! (Right is customer ))
Throw new argumentexception ("argument not a customer ",
"Right ");
Customer rightcustomer = (customer) right;
Return compareto (rightcustomer );
}
// Type-safe compareto.
// Right is a customer, or derived from customer.
Public int compareto (customer right)
{
Return _ name. compareto (right. _ name );
}
# Endregion
}
Icomparable. compareto () is now an explicit interfaceimplementation; it can be called only through an icomparable reference. users of your customer struct will get the type-safe comparison, and the unsafe comparison is inaccessible. the following innocent mistake no longer compiles:
Customer C1;
Employee E1;
If (c1.compareto (E1)> 0)
Console. writeline ("customer one is greater ");
It does not compile because the arguments are wrong for the public customer. compareto (customer right) method. the icomparable. compareto (object right) method is not accessible. you can access the icomparable method only by explicitly casting the reference:
Customer C1;
Employee E1;
If (C1 as icomparable). compareto (E1)> 0)
Console. writeline ("customer one is greater ");
When you implement icomparable, use explicit interface implementation and provide a strongly typed public overload. the stronugly typed overload improves performance and decreases the likelihood that someone will misuse the compareto method. you won't see all the benefits in the sort function that. net Framework uses because it will still access compareto () through the interface pointer (see item 19), but code that knows the type of both objects being compared will get better performance.
We'll make one last small change to the customer struct. The C # language lets you overload the standard Relational operators. Those shocould make use of the Type-safe compareto () method:
Public struct Customer: icomparable
{
Private string _ name;
Public customer (string name)
{
_ Name = Name;
}
# Region icomparable members
// Icomparable. compareto ()
// This is not type safe. The runtime type
// Of the right parameter must be checked.
Int icomparable. compareto (object right)
{
If (! (Right is customer ))
Throw new argumentexception ("argument not a customer ",
"Right ");
Customer rightcustomer = (customer) right;
Return compareto (rightcustomer );
}
// Type-safe compareto.
// Right is a customer, or derived from customer.
Public int compareto (customer right)
{
Return _ name. compareto (right. _ name );
}
// Relational operators.
Public static bool operator <(customer left,
Customer right)
{
Return left. compareto (right) <0;
}
Public static bool operator <= (customer left,
Customer right)
{
Return left. compareto (right) <= 0;
}
Public static bool operator> (customer left,
Customer right)
{
Return left. compareto (right)> 0;
}
Public static bool operator> = (customer left,
Customer right)
{
Return left. compareto (right)> = 0;
}
# Endregion
}
That's all for the standard order of MERs: by name. later, you must create a report sorting all MERs by revenue. you still need the normal comparison functionality defined by the customer struct, sorting them by name. you can implement this additional ordering requirement by creating a class that implements the icomparer interface. icomparer provides the standard way to provide alternative orders for a type. any of the Methods inside. net FCL that work on icomparable types provide overloads that order objects through icomparer. because You authored the customer struct, you can create this new class (revenuecomparer) as a private nested class inside the customer struct. it gets exposed through a static property in the customer struct:
Public struct Customer: icomparable
{
Private string _ name;
Private double _ revenue;
// Code from earlier example elided.
Private Static revenuecomparer _ revcomp = NULL;
// Return an object that implements icomparer
// Use lazy evaluation to create just one.
Public static icomparer revenuecompare
{
Get
{
If (_ revcomp = NULL)
_ Revcomp = new revenuecomparer ();
Return _ revcomp;
}
}
// Class to compare MERs by revenue.
// This is always used via the interface pointer,
// So only provide the interface override.
Private class revenuecomparer: icomparer
{
# Region icomparer members
Int icomparer. Compare (object left, object right)
{
If (! (Left is customer ))
Throw new argumentexception (
"Argument is not a customer ",
"Left ");
If (! (Right is customer ))
Throw new argumentexception (
"Argument is not a customer ",
"Right ");
Customer leftcustomer = (customer) left;
Customer rightcustomer = (customer) right;
Return leftcustomer. _ revenue. compareto (
Rightcustomer. _ revenue );
}
# Endregion
}
}
the last version of the customer struct, with the embedded revenuecomparer, lets you order a collection of MERs by name, the natural order for customers, and provides an alternative order by exposing a class that implements the icomparer interface to order MERs by revenue. if you don't have access to the source for the customer class, you can still provide an icomparer that orders MERs using any of its public properties. you shoshould use that idiom only when you do not have access to the source for the class, as when you need a different ordering for one of the classes in. net Framework.
nowhere in this item did I mention equals () or the = Operator (see item 9 ). ordering relations and equality are distinct operations. you do not need to implement an algorithm ity comparison to have an ordering relation. in fact, reference types commonly implement Ordering Based on the object contents, yet implement priority ity Based on Object Identity. compareto () returns 0, even though equals () returns false. that's perfectly legal. equality and ordering relations are not necessarily the same.
icomparable and icomparer are the standard mechanisms for providing ordering relations for your types. icomparable shoshould be used for the most natural ordering. when you implement icomparable, you shoshould overload the comparison operators (<, >,<=, >=) consistently with our icomparable ordering. icomparable. compareto () uses system. object parameters, So you shoshould also provide a type-specific overload of the compareto () method. icomparer can be used to provide alternative orderings or can be used when you need to provide ordering for a type that does not provide it for you.