More effective tive C # Item 7. Do not create generic specialization on base classes or interfaces

Source: Internet
Author: User

 

Item 7. Do not create generic specialization on base classes or interfaces

Introducing generic methods can make it highly complicated for the compiler to Resolve Method overloads. each generic method can match any possible substitute for each type parameter. depending on how careful you are (or aren't), your application will behave very strangely. when you create generic classes or methods, you are responsible for creating a set of methods that will enable developers using that class to safely use your code with minimal confusion. this means that you must pay careful attention to overload resolution, and you must determine when generic methods will create better matches than the methods developers might reasonably failed CT.

Generics are good techniques, but if we are not careful enough, they are prone to misuse, especially when we reload functions. The following is an example.

Examine this code, and try to guess the output:

public class MyBase{}public interface IMessageWriter{    void WriteMessage();}public class MyDerived : MyBase, IMessageWriter{    #region IMessageWriter Members    void IMessageWriter.WriteMessage()    {        Console.WriteLine("Inside MyDerived.WriteMessage");    }    #endregion}public class AnotherType : IMessageWriter{    #region IMessageWriter Members    public void WriteMessage()    {        Console.WriteLine("Inside AnotherType.WriteMessage");    }    #endregion}class Program{    static void WriteMessage(MyBase b)    {        Console.WriteLine("Inside WriteMessage(MyBase)");    }    static void WriteMessage<T>(T obj)    {        Console.Write("Inside WriteMessage<T>(T):  ");        Console.WriteLine(obj.ToString());    }    static void WriteMessage(IMessageWriter obj)    {        Console.Write(            "Inside WriteMessage(IMessageWriter):  ");        obj.WriteMessage();    }    static void Main(string[] args)    {        MyDerived d = new MyDerived();        Console.WriteLine("Calling Program.WriteMessage");        WriteMessage(d);        Console.WriteLine();        Console.WriteLine(            "Calling through IMessageWriter interface");        WriteMessage((IMessageWriter)d);        Console.WriteLine();        Console.WriteLine("Cast to base object");        WriteMessage((MyBase)d);        Console.WriteLine();        Console.WriteLine("Another Type test:");        AnotherType anObject = new AnotherType();        WriteMessage(anObject);        Console.WriteLine();        Console.WriteLine("Cast to IMessageWriter:");        WriteMessage((IMessageWriter)anObject);    }}

In this example, there are several reloads: the overload of generic parameters, the overload of interface parameters, and the overload of basic parameters.

Some of the comments might make it a giveaway, but make your best guess before looking at the output. it's important to understand how the existence of generic methods affects the method resolution rules. generics are almost always a good match, and they wreak havoc with our assumptions about which methods get called. here's the output:

I admit, I guess the ending, but I didn't guess the beginning. Note the influence of generic methods on method calling inference of compilers. That is to say, when a generic method exists, how does the compiler determine which method to call. Here we also confirm that flexibility brings complexity. The complexity here does not necessarily mean the complexity of the code structure, but the complexity of the call relationship.

Calling Program.WriteMessageInside WriteMessage<T>(T):  Item14.MyDerivedCalling through IMessageWriter interfaceInside WriteMessage(IMessageWriter):    Inside MyDerived.WriteMessageCast to base objectInside WriteMessage(MyBase)Another Type test:Inside WriteMessage<T>(T):  Item14.AnotherTypeCast to IMessageWriter:Inside WriteMessage(IMessageWriter):    Inside AnotherType.WriteMessage

 

The first test shows one of the more important concepts to remember:Writemessage <t> (t obj)Is a better matchWritemessage (mybase B)For an object that is derived fromMybase. That's because the compiler can make an exact match by substitutingMyderivedForTIn that message, andWritemessage (mybase)Requires an implicit conversion. The generic method is better. This concept will become even more important when you see the extension methods defined inQueryableAndEnumerableClasses added in C #3.0. generic methods are always perfect matches, so theyWinOver base class methods.

Simply put, if there are two overload methods, their parameter types areBase ClassAndGenericWhen the input parameter isSubclass objectThe compiler will choose to callGeneric overload method. It can be understood from the idea of the compiler parsing method call, that is, the method that the compiler calls is easier to match. If you do not know this and infer code behavior, an error may occur.

 

The next two tests show how you can control this behavior by explicitly invoking the conversion (eitherMybaseOr toImessagewriterType). And the last two tests show that the same type of behavior is present for interface implementations even without class inheritance.

For generics, base classes, and interface overloading, the compiler preferentially matches the generics. If we don't want to choose the compiler method, we need explicit type conversion.

 

Name resolution rules are interesting, and you can show off your arcane knowledge about them at geek cocktail parties. but what you really need is a strategy to create code that ensures that your concept of "best match" agrees with the compiler's concept. after all, the compiler always wins this battle.

Best match is made based on compiler rules.

 

It's not a good idea to create generic specializations for base classes when you intend to support the class and all its descendents. It's equally error prone to create generic specializations for interfaces.

It can be seen that it is not good to create generic specializations for base classes and interfaces. The reason is that the calling of generic methods is confusing due to the existence of the inheritance relationship.

 

But numeric types do not present those pitfalls. there is no inheritance chain between integral and floating-point numeric types. as Item 2 explains, often there are good reasons to provide specific versions of a method for different value types. specifically,. net Framework includes specialization on all numeric typesEnumerable. max <t>,Enumerable. Min <t>, And similar methods.

There is no such problem with the value type because the value type has no inheritance relationship.

 

But it's best to use the compiler instead of adding runtime checks to determine the type. That's what you're trying to avoid by Using Generics in the first place, right?

Of course, we can also use the runtime type check to clarify this confusing logic. The following is an example.

// Not the best solution// this uses runtime type checkingstatic void WriteMessage<T>(T obj){    if (obj is MyBase)        WriteMessage(obj as MyBase);    else if (obj is IMessageWriter)        WriteMessage((IMessageWriter)obj);    else    {        Console.Write("Inside WriteMessage<T>(T):  ");        Console.WriteLine(obj.ToString());    }}

 

This code might be fine, but only if there are only a few conditions to check. it does hide all the ugly behavior from your MERs, but notice that it introduces some runtime overhead. your generic method is now checking specific types to determine whether they are (in your mind) a better match than the one the compiler wowould choose if left to its own devices.

Obviously, this code can determine different logic based on the parameter type. It does meet our usage expectations. The disadvantage is that it is an additional overhead to perform type check during runtime. If possible, let the compiler do its best to judge the type. (So we need to know the Judgment Rules of the compiler.) A programmer in charge always needs to consider the performance of the Code. Both readability, complexity, and performance need to be weighed.

 

Use this technique only when it's clear that a better match is quite a bit better, and measure the performance to see whether there are better ways to write your library to avoid the problem altogether.

In short, do not use the runtime check whenever possible, and use the compiler for type determination first.

 

Of course, this is not to say that you should create more-specific methods for a given implementation. item 3 shows how to create a better implementation when advanced capabilities are available. the code in item 3 creates a reverse iterator that adapts itself correctly when advanced capabilities are created. notice that the item 3 Code does not rely on generic types for any name resolution. each constructor expresses the varous capabilities correctly to ensure that the proper method can be called at each location.

That is, there is no such thing as absolute, clever Author :)

 

However, if you want to create a specific instantiation of a generic method for a given type, you need to create that instantiation for that type and all its descendents. if you want to create a generic specialization for an interface, you need to create a version for all types that implement that interface.

If we want to create a generic method for a known type that is a base class or interface, we need to create two versions, one is the generic method for this base class or interface, and the other is the generic method for the subclass or implementation class of this interface.

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.