In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article mainly explains "how to use generics in Effective C #". The content in the article is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought to study and learn how to use generics in Effective C #.
Define only the constraints that are just enough
Generic constraints can specify what type parameters a generic class must take in order to function properly. When setting constraints, it is not appropriate to be too broad or too strict. If there are no constraints at all, the program must do a lot of checks at run time and perform more cast operations. And when the compiler generates IL code for the definition of this generic type, you can provide more hints through constraints. If you don't give any hints, then the compiler has to assume that these type parameters represent the most basic System.Object, that is, assuming that the actual types in the future only support those methods published by System.Object. This makes any usage that is not defined in System.Object cause the compiler to report an error, and even the most basic operations such as new T () are not supported.
But don't be so strict when adding constraints that you limit the scope of use of generic classes, just add constraints that are really necessary.
When creating generic classes, you should provide support for type parameters that implement IDisposable
If you create an instance based on type parameters in a generic class, you should determine whether the type to which the instance belongs implements the IDisposable interface. If implemented, the relevant code must be written to prevent the program from leaking resources after leaving the generic class. This is also divided into different situations: the method of a generic class creates an instance based on the type represented by the type parameter and uses the instance similar to the following. If T is an unmanaged resource, it will cause a memory leak:
Public interface IEngine {void DoWork ();} public class EngineDriver where T: IEngine, new () {public void GetThingsDone () {var driver = new T (); driver.DoWork ();}}
The correct way to write it should be:
Var driver = new T (); using (driver as IDisposable) {driver.DoWork ();}
The compiler treats driver as an IDisposable and creates hidden local variables to hold references to that IDisposable. In the case where T does not implement IDisposable, the value of this local variable is null, and the compiler does not call Dispose () because it checks before calling it. Conversely, if T implements IDisposable, the compiler generates code to call the Dispose () method when the program exits the using block. This code is equivalent to:
Var a = driver as IDisposable;driver.DoWork (); a?.Dispose ()
After using using, it is important to note that all operations that call the driver instance cannot be placed after the using area, because the driver has already been released.
The generic class treats the instance created based on the type parameter as a member variable, in which case the code is more complicated. The object type pointed to by this reference to this class may or may not implement the IDisposable interface, but in order to deal with the situation where the IDisposable interface may be implemented, the generic class itself must implement IDisposable, and to determine whether the relevant resource implements this interface, if it does, call the resource's Dispose () method.
Public class EngineDriver2: IDisposable where T: IEngine, new () {/ / it's expensive to create, so create to null private Lazy driver = new Lazy (() = > new T ()); public void GetThingsDone () = > driver.Value.DoWork (); public void Dispose () {if (driver.IsValueCreated) {var resource = driver.Value as IDisposable; resource?.Dispose ();}
Or you can transfer ownership of the driver outside the class, so you don't have to worry about the release of resources. | |
Public sealed class EngineDriver3 where T: IEngine {private T driver; public EngineDriver3 (T driver) {this.driver = driver;}} if there are generic methods, do not create an overloaded version of the base class or interface
If there are multiple methods that are overloaded with each other, the compiler needs to determine which method should be called. After the introduction of the generic method, this set of judgment rules will become more complex, because as long as the type parameters can be replaced, they can match the generic method. For example, there are three types, and the relationship between them is shown in the code:
Public class MyBase {} public interface IMsgWriter {void WriteMsg ();} public class MyDerived: MyBase, IMsgWriter {void IMsgWriter.WriteMsg () = > Console.WriteLine ("Inside MyDerived.WriteMsg");}
Next, three overloaded methods are defined, including generic methods:
Static void WriteMsg (MyBase b) {Console.WriteLine ("Inside WriteMsg (MyBase b)");} static void WriteMsg (T obj) {Console.WriteLine ("Inside WriteMsg (T obj)";} static void WriteMsg (IMsgWriter obj) {Console.Write ("Inside WriteMsg (IMsgWriter obj)");}
So what is the result of the following three kinds of call writing?
MyDerived derived = new MyDerived (); WriteMsg (derived); var msgWriter = derived as IMsgWriter;WriteMsg (msgWriter); var mbase = derived as MyBase;WriteMsg (mbase)
The following is the running result, is it consistent with your expectation?
Inside WriteMsg (T obj) Inside WriteMsg (IMsgWriter obj) Inside WriteMsg (MyBase b)
The first result shows a very important phenomenon: if the class to which the object belongs inherits from the base class MyBase, when calling WriteMsg with that object as a parameter, WriteMsg will always get a match before WriteMsg (MyBase b). This is because if you want to match the generic version of the method, the compiler can directly regard the subclass MyDerived as the type parameter T, but to match the base version of the method. You must implicitly convert a MyDerived object to a MyBase object, so it thinks the generic version of WriteMsg is better. If you want to call WriteMsg (MyBase b), you need to explicitly convert MyDerived-type objects to MyBase-type objects, just like the third test.
If you do not need to set the object represented by the type parameter as an instance field, you should give priority to creating a generic method rather than a generic class
In general, our usual habit is to define generic classes, but sometimes it is more recommended to use generic methods. Because the generic parameters provided when using a generic method only need to match the requirements of the method, while the generic parameters provided when using a generic class must meet every constraint defined by that class. If code is to be added to the class in the future, more constraints may be imposed on generic parameters at the class level, making the application of the class narrower and narrower.
In addition, generic methods are more flexible than generic classes, such as the following generic utility class acquisition provides a way to get a larger value:
Public class Utils {public static T Max (T left, T right) {return Comparer.Default.Compare (left, right) > 0? Left: right;}}
Because it is generic, the type is provided for each call:
Utils.Max ("c", "d"); Utils.Max (4,3)
In this way, although the implementation of the class itself is more convenient, it is more troublesome to use the caller. More importantly, the value type can directly use Math.Max, instead of having to let the program determine whether the relevant type implements IComparer every time before calling the appropriate method. Math.Max can provide better performance, so it can be improved to provide different versions of the Max method for value types:
Public class Utils1 {public static T Max (T left, T right) {return Comparer.Default.Compare (left, right) > 0? Left: right;} public static int Max (int left, int right) {return Math.Max (left, right) > 0? Left: right;} public static double Max (double left, double right) {return Math.Max (left, right) > 0? Left: right;}}
After this modification, the generic class is changed to partially use generic methods. For int and double, the compiler will directly call the non-generic version, and other types will match to the generic version.
Utils1.Max ("c", "d"); Utils1.Max (4,3)
Another advantage of writing this is that if some specific versions for other types are added in the future, the compiler will not call the generic version when dealing with the parameters of those types, but will directly call the corresponding specific version.
However, it should also be noted that not every generic algorithm can be implemented simply in the form of generic methods, bypassing generic classes. In two cases, a class must be written as a generic class:
This class needs to use a value as its internal state, and the type of the value must be expressed as a generic (such as a collection class).
This class needs to implement the generic version of the interface.
In addition to other situations, you can usually consider using non-generics that contain generic methods.
Define only the necessary contracts in the interface and leave other functions to the extension methods to implement
If there are many classes in the program that must implement an interface to be designed, you should define as few methods as possible when defining the interface, and then you can write some convenient methods for the interface in the form of extension methods. This not only enables those who implement the interface to write less code, but also enables those who use the interface to take full advantage of those extension methods.
However, one thing to note when using extension methods: if you have defined an extension method for an interface, and some other classes want to implement the method of the same name in their own way, then the extension method will be overwritten, like the following, the extension method NextMarker is defined for IFoo, and NextMarker is also implemented in MyType.
Public interface IFoo {int Marker {get; set;}} public static class FooExtension {public static void NextMarker (this IFoo foo) {foo.Marker++;}} public class MyType: IFoo {public int Marker {get; set;} public void NextMarker () {this.Marker + = 5;}}
Then the result of the following code is 5, not 1
Var myType = new MyType (); myType.NextMarker (); Console.WriteLine (myType.Marker); / / 5
If you need to call the extension method, you need to explicitly convert myType to IFoo.
Var myType = new MyType (); var a = myType as IFoo;a.NextMarker (); thank you for reading, this is the content of "how to use generics in Effective C#". After the study of this article, I believe you have a deeper understanding of how to use generics in Effective C#, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.