C # is a strong language. In general, we 'd better avoid forcibly converting a type to another type. However, sometimes the type check during running is unavoidable. I believe you have written many functions with the system. Object type as parameters, because the. NET Framework defines the signatures of these functions in advance. Within these functions, we often need to transform those parameters down to other types, classes, or interfaces. For this transformation, we usually have two options: Use the as operator, or use the traditional C-style forced transformation. There is also a more safe way: first use is for a conversion test, and then use the as operator or forced conversion.
The correct choice should be to use the as operator as much as possible, because it is safer than forced transformation, and it also has better efficiency at the runtime level. Note that the AS and is operators do not execute any user-defined conversions. The conversion is successful only when the type at run time matches the target conversion type. They will never construct new objects during the conversion process.
Let's look at an example. If you want to convert any object to a mytype instance. We may do this as follows:
Object o = factory. GetObject ();
// First version:
Mytype T = O as mytype;
If (T! = NULL)
{
// Process T. The current type of T is mytype.
} Else
{
// Report the transformation failure.
}
Alternatively, you can do the following:
Object o = factory. GetObject ();
// Version 2:
Try {
Mytype T;
T = (mytype) O;
If (T! = NULL)
{
// Process T. The current type of T is mytype.
} Else
{
// Report null reference failure.
}
} Catch
{
// Report the transformation failure.
}
I believe everyone agrees that the transformation code of the first version is simpler and easier to read. No additional try/catch statements are added, which avoids the burden. Note: In the second version, in addition to capturing exceptions, we also need to check the null condition, because if o is null, the forced transformation can convert it to any reference type. However, if it is an as operator and the converted object is null, the execution result returns NULL. Therefore, if forced transformation is used, we need to check whether it is null and capture exceptions. If you use the as operator, you only need to check whether the returned reference is null.
The biggest difference between cast and as operators is how to handle custom conversions. The AS and is operators only check the runtime type of the converted object and do not perform other operations. If the runtime type of the converted object is neither the target type nor its derived type, the transformation will fail. However, forced transformation uses the conversion operator to perform the transformation operation, which includes any built-in Numerical Conversion. For example, if a long type is forcibly converted to a short type, some information is lost.
Clause 3: operator is or as is superior to forced transformation 21 |
|
The same problem occurs when we use User-Defined conversions. Let's look at the following code:
Public class secondtype
{
Private mytype _ value;
// Ignore other details.
// Conversion operator.
// Convert secondtype to mytype. For more information, see section 29. [4]
Public Static Implicit Operator
Mytype (secondtype T)
{
Return T. _ value;
}
}
Assume that factory. GetObject () in the first line of code below returns a secondtype object:
Object o = factory. GetObject ();
// O is a secondtype:
Mytype T = O as mytype; // transformation failed. The O type is not mytype.
If (T! = NULL)
{
// Process T. The current type of T is mytype.
} Else
{
// Report the transformation failure.
}
// Version 2:
Try {
Mytype T1;
T1 = (mytype) O; // transformation failed. The O type is not mytype.
If (T1! = NULL)
{
// Process T1 and T1. The current type is mytype.
} Else
{
// Report null reference failure.
}
} Catch
{
// Report the transformation failure.
}
The transformation operations of both versions failed. As you may remember, I have mentioned before that forced transformation will implement user-defined conversion. Some readers believe that the version of forced transformation will succeed. There is no error in this thinking, but the compiler is based on the class type of object o during compilation when generating code. The compiler knows nothing about the O runtime type-the compiler only knows that the O type is system. object. Therefore, the compiler only checks whether user-defined conversions that convert system. object to mytype exist. This will be checked in the definition of system. Object and mytype. Since no user-defined conversion is found, the compiler will generate code to check the runtime type of O and compare it with mytype. The transformation will fail because o's runtime type is secondtype. The compiler does not check whether custom conversions exist between secondtype and mytype at O runtime.
Of course, if the above Code is modified as follows, the conversion will be successful:
Object o = factory. GetObject ();
// Version 3:
Secondtype ST = O as secondtype;
Try {
Mytype T;
T = (mytype) ST;
If (T! = NULL)
{
// Process T. The current type of T is mytype.
} Else
{
// Report null reference failure.
}
} Catch
{
Clause 3: operator is or as is superior to forced transformation 24 |
|
// Report the transformation failure.
}
In formal development, we must not write such ugly code, but it reveals the problem to us. Although it is never possible for everyone to write code like above, you can use a function with the system. Object type as the parameter, so that the function can perform the correct conversion internally.
Object o = factory. GetObject ();
Dostuffwithobject (O );
Private void dostuffwithobject (Object O2)
{
Try {
Mytype T;
T = (mytype) O2; // transformation failed. The O type is not mytype.
If (T! = NULL)
{
// Process T. The current type of T is mytype.
} Else
{
// Report null reference failure.
}
} Catch
{
// Report the transformation failure.
}
}
Remember, the User-Defined conversion operator only applies to the object's compile-time type, rather than the runtime type. It is not important to determine whether there is a conversion between the runtime type and mytype of O2. In fact, the compiler does not know or care about this. For the following statement, if the declaration type of St is different, the statement will behave differently:
T = (mytype) ST;
However, for the following statement, no matter what the st declaration type is, the same result will be generated [5]. Therefore, we say that the as operator is better than the forced transformation-it has relatively consistent transformation results.
However, if there is no inheritance relationship between the types of the as operator, even if the user-defined conversion operator exists, it will generate a compilation error. For example, the following statement:
T = sT as mytype;
We already know that we should use the as operator whenever possible during the transformation. Let's talk about some situations where the as operator cannot be used. First, the as operator cannot be applied to value types. For example, the following code will report an error during compilation:
Object o = factory. getvalue ();
Int I = O as int; // cannot be compiled.
This is because int is a value type, so it cannot be null. If O is not an integer, what else can I store? Any stored value must be a valid integer, so as cannot be used together with the value type. We can only use forced Transformation:
Object o = factory. getvalue ();
Int I = 0;
Try {
I = (INT) O;
} Catch
{
I = 0;
}
However, this is not the only way to do this. We can also use the is statement to avoid the exception check or force transformation:
Object o = factory. getvalue ();
Int I = 0;
If (O is int)
I = (INT) O;
If O is another type that can be converted to int, such as double, the is Operator returns false. If the O value is null, the is Operator returns false.
The is operator should be used only when we cannot use the as operator for type conversion. Otherwise, using is will cause code redundancy:
// Correct, but redundant:
Object o = factory. GetObject ();
Mytype T = NULL;
Clause 3: operator is or as is superior to forced transformation 26 |
|
If (O is mytype)
T = O as mytype;
The above code is actually the same as the following code:
// Correct, but redundant:
Object o = factory. GetObject ();
Mytype T = NULL;
If (O as mytype )! = NULL)
T = O as mytype;
This is obviously inefficient and redundant. If we plan to use as for transformation, it is unnecessary to use the is check. Simply compare the operation result of the as operator with null, which is relatively simple.
Now that we understand the differences between the is operator, the as operator, and the forced transformation, Let's guess which operator is used in the foreach loop statement to execute type conversion?
Public void usecollection (ienumerable thecollection)
{
Foreach (mytype t in thecollection)
T. dostuff ();
}
The answer is forced transformation. In fact, the following code is the same as the result of the preceding foreach statement Compilation:
Public void usecollection (ienumerable thecollection)
{
Ienumerator it = thecollection. getenumerator ();
While (it. movenext ())
{
Mytype t = (mytype) It. Current;
T. dostuff ();
}
}
Forced transformation is used because the foreach statement must support both the value type and the reference type. No matter what type the conversion target is, the foreach statement can show the same behavior. However, because forced transformation is used, the foreach statement may generate a badcastexception [6].
Because ienumerator. Current returns system. object, and the object does not define any conversion operators, you do not need to consider the conversion operators. If a set of secondtype objects is used in the usecollection () function, the transformation will fail because the foreach statement uses forced transformation, however, forced transformation does not care about the runtime class type of collection elements. It only checks whether there is a conversion between the system. object class (the type returned by ienumerator. Current) and the declared type mytype of the cyclic variable.
Finally, sometimes we may want to know the exact type of an object, regardless of whether it can be converted to another type. If one type inherits from another type, the is Operator returns true. Use the GetType () method of system. object to obtain the runtime type of an object. This method can be used to test the type more strictly than is or as, because we can compare the type of the object it returns with a specific type.
Let's take a look at the following functions:
Public void usecollection (ienumerable thecollection)
{
Foreach (mytype t in thecollection)
T. dostuff ();
}
If you create a newtype class that inherits from mytype, you can apply a set of Newtype objects to the usecollection function.
Public class newtype: mytype
{
// Ignore implementation details.
}
If we plan to write a function to process all instance objects compatible with the mytype type, the usecollection function shows a good solution. However, if the function to be compiled only processes objects whose runtime type is mytype, you should use the GetType () method to perform Exact Tests on the type. We can put this test in the foreach loop. The most common part of a runtime type test is equal judgment (see clause 9 ). For the vast majority of other cases, the. isinst provided by the AS and is operators compares [7] In terms of semantics.
Clause 4: Use the conditional feature instead # If condition compilation 27 |
|
Good object-oriented practices generally tell us to avoid transformation, but sometimes we have no choice. We should try our best to use the AS and is operators provided in C # language to better express our visualization. Different transformation methods have different rules. The is and as operators can meet our requirements in most cases. They will succeed only when the tested objects are of the correct type. In general, do not use forced transformation, because it may bring unexpected negative effects, and success or failure is often unexpected.