50 suggestions for improving C # programming (31-35)

Source: Internet
Author: User

------------------------- Translation By Cryking -----------------------------
----------------------- Please indicate the source for reprinting. Thank you! ------------------------

Digression: The book "Covers C #4.0" is about to be translated. Due to the limited level, errors are inevitable, especially in many places, I feel that the original author is sometimes too arrogant.) or I have added some of my own opinions, so I would like to tell them clearly.

PS, please respect the fruits of your work. Thank you!


31 use IComparable And IComparer Implement sequential relationship
Your type requires a sequential relationship to describe how to sort and query collection elements .. NET Framework defines IComparable And IComparer These two Interfaces describe the sequential relationship. You can define your own relational operation implementation (>,<,<=, >=) to provide comparison of the specified type.
The IComparable Interface contains a method: CompareTo (), IComparable It is used in the latest API of. NET, while the old API uses the IComparable interface. Therefore, to maintain compatibility, When you implement IComparable You should also implement IComparable. IComparable with System. Object parameters:

  public struct Customer : IComparable
 
  , IComparable    {        private readonly string name;        public Customer(string name)        {            this.name = name;        }        #region IComparable
  
    Members        public int CompareTo(Customer other)        {            return name.CompareTo(other.name);        }        #endregion        #region IComparable Members        int IComparable.CompareTo(object obj)        {            if (!(obj is Customer))                throw new ArgumentException("Argument is not a Customer", "obj");            Customer otherCustomer = (Customer)obj;            return this.CompareTo(otherCustomer);        }        #endregion    }
  
 


Note that IComparable is displayed here. Because its parameter type is object, you need to check the parameter type during runtime. (obj is Customer), and improper parameters will produce unpacking and packing, resulting in additional runtime overhead.
Customer c1;
Employee e1;
If (c1.CompareTo (e1)> 0)
Console. WriteLine ("Customer one is greater ");
The public int CompareTo (Customer other) method is called here, and the Code cannot be compiled because the Employee class cannot be converted to the Customer type. You must call the IComparable. CompareTo (object obj) method to compare the display conversions:
Customer c1 = new Customer ();
Employee e1 = new Employee ();
If (c1 as IComparable). CompareTo (e1)> 0)
Console. WriteLine ("Customer one is greater ");
Added operator support for Customer:
    // 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;    }


32 avoid ICloneable Interface
The ICloneable interface sounds good: You can implement this interface to support type replication. Once a type supports ICloneable, all its derived classes must also support it, and all its member types must also support ICloneable or there are other ways to create a copy. In the end, it is very difficult to support deep copy when you create a design that contains web objects. ICloneable supports both deep copy and light copy. A shallow copy is a new object that creates and copies all member variables. If these member variables are of the reference type, the new object points to the one referenced by the original object. Deep copy is a new object that creates and copies all member variables. All reference type members also follow this method for Recursive copying (the content of referenced type variables will be copied ). For built-in types (such as integer), The results returned by deep copy and light copy are the same. In many cases, avoiding ICloneable can simplify the class and make it easier to implement and use the class.
Any value type that only contains the built-in type does not need to support ICloneable. A simple assignment will copy all values, which is more efficient than Clone. Clone () must bind its return type to System. Object reference. However, if the value type includes the reference type
What about it?
Public struct ErrorMessage
{
Private int errCode;
Private int details;
Private string msg;
// Details elided
}
Here, the string type is a special example because it is an unchangeable type. Generally, creating a struct containing any reference type is more complex and rare. The value assignment built in struct creates a shallow copy. If you want to create a deep copy, you need to clone the reference type members it contains, in addition, you need to support the deep copy (with the Clone () method) for these reference type members ). Even so, it can only work when its reference type members support ICloneable, And the contained Clone () method implements deep copy.
Let's consider the reference type:
    class BaseType : ICloneable    {        private string label = "class name";        private int[] values = new int[10];        public object Clone()        {            BaseType rVal = new BaseType();            rVal.label = label;            for (int i = 0; i < values.Length; i++)                rVal.values[i] = values[i];            return rVal;        }    }    class Derived : BaseType    {        private double[] dValues = new double[10];        static void Main(string[] args)        {            Derived d = new Derived();            Derived d2 = d.Clone() as Derived;            if (d2 == null)                Console.WriteLine("null");        }    }

If you run this program, you will find that the d2 value is null. The derived class inherits the ICloneable of the base class. the Clone () method is incorrect for the derived class because it only clones the base class, And the Clone () method of the base class only creates the base class object, instead of a derived class object. You can create an abstract Clone () method in the base class to force all the derived classes to implement it. In this way, you need to define a method for the derived class to create a copy of the base class members, A replication constructor that defines a protected is usually used, for example:
class BaseType    {        private string label;        private int[] values;        protected BaseType()        {            label = "class name";            values = new int[10];        }        // Used by devived values to clone        protected BaseType(BaseType right)        {            label = right.label;            values = right.values.Clone() as int[];        }    }    sealed class Derived : BaseType, ICloneable    {        private double[] dValues = new double[10];        public Derived()        {            dValues = new double[10];        }        // Construct a copy        // using the base class copy ctor        private Derived(Derived right) :base(right)        {            dValues = right.dValues.Clone()            as double[];        }        public object Clone()        {            Derived rVal = new Derived(this);            return rVal;        }    }


Here, the base class does not need to implement ICloneable. All leaf classes should be sealed and ICloneable should be implemented when necessary.
ICloneable is useful, but it is only an exception rather than a rule. This is also because ICloneable is not added when the. NET Framework is updated to support generics. The meaning of generics. You should never add support for ICloneable for the value type. When a replication operation is really needed, you should add ICloneable support for the leaf class. ICloneable should be avoided in other cases.


33 when the new modifier is used only to update the corresponding base class Method
You can use the new modifier on a non-Virtual Member inherited from the base class to redefine the member. What can you do?
It does not mean you should do this. Redefinition of non-Virtual Methods creates a fuzzy behavior.
Assume that the following code executes the same thing:
Object c = MakeObject ();
// Call through MyClass reference:
MyClass cl = c as MyClass;
Cl. MagicMethod ();
// Call through MyOtherClass reference:
Mytherclass cl2 = c as mytherclass;
Cl2.MagicMethod ();
This is not the case when the new modifier is involved:
    public class MyClass    {        public void MagicMethod()        {            // details elided.        }    }    public class MyOtherClass : MyClass    {        // Redefine MagicMethod for this class.        public new void MagicMethod()        {            // details elided        }    }


This method causes many developers to be confused. If you call the same function on the same object, you expect the same code to be executed. The fact has changed. You have modified the function behavior using new, which leads to inconsistency between the base class and the base class. The new modifier adds a different method to your class namespace.
The non-virtual method is statically bound, and the virtual function is dynamically bound. It dynamically calls the correct function of the object type at runtime.
There is only one exception. In this case, you may need to use the new modifier. You can add a new modifier to merge new versions of the base class.
The base class may contain the method names you have used, for example:
public class MyWidget : BaseWidget{  public void new NormalizeValues() {    // details elided.    // Call the base class only if (by luck)    // the new method does the same operation.    base.NormalizeValues(); }}

34. avoid overloading the methods defined in the base class.
When a base class has defined the member name, it also gives the corresponding meaning of the name. A derived class should not use the same name to complete different functions. However, in some cases, the derived class may still want to use the same name as the base class member, it wants to use parameters or methods with the same name to implement the same meaning as the base class member name (the virtual method is designed to implement the same meaning in different ways ). You should not reload the declared methods of the base class.
In C # language, the overloaded rules must be complex, because the overloaded methods can be declared in the target Derived classes, base classes, any extension methods, interfaces, and so on.
Adding generic methods and generic extension methods makes it more complicated. To reduce this complexity, we should try to avoid the method of overloading the base class. Why must I reload it? We can use a different method name. Note the differences between Overloading and rewriting: rewriting is a virtual method that overwrites the base class, while Overloading is a method that creates multiple methods with the same name and different parameter types or numbers.
Let's take a look at the problems with the overload base class method:
Assume that the class hierarchy is as follows:
Public class B2 {}
Public class D2: B2 {}
Then there is a class as follows:
Public class B
{
Public void Foo (D2 parm)
{
Console. WriteLine ("In B. Foo ");
}
}
Var obj1 = new D ();
Obj1.Foo (new D2 (); // output In B. Foo
Now let's add a derived class to overload the base class method:
Public class D: B
{
Public void Foo (B2 parm)
{
Console. WriteLine ("In D. Foo ");
}
}
Then run the Code:
Var obj2 = new D ();
Obj2.Foo (new D2 ());
Obj2.Foo (new B2 ());
What is your expected output? Both calls output "In D. foo ". what you may want is the second call to output "In B. foo ", but the result is not so, they all call the D method.
B obj3 = new D ();
Obj3.Foo (new D2 ());
The above code will output "In B. Foo ". Because it is converted to B type after instantiating D, the subsequent method will naturally call B.
Var obj4 = new D ();
(B) obj4). Foo (new D2 ());
Obj4.Foo (new B2 ());
In this way, we can output "In B. Foo" and "In D. Foo.
Many people will be confused about the above Code, so we should not reload the methods of the base class to avoid unnecessary complexity. We only need to replace a name to solve the problem clearly, why not?
Public class B
{
Public void Foo (D2 parm)
{
Console. WriteLine ("In B. Foo ");
}
Public void Bar (B2 parm)
{
Console. WriteLine ("In B. Bar ");
}
}


35. Learn how to implement the parallel algorithm of PLINQ
Multi-core programming is not simple, but PLINQ makes it simpler.
For example, calculate the factorial of the first 150 numbers as follows:
Var nums = data. Where (m => m <150). Select (n => Factorial (n ));
Rewrite to parallel as follows:
Var numsParallel = data. AsParallel (). Where (m => m <150). Select (n => Factorial (n ));
That is to say, simply add an AsParallel () method and rewrite it to the LINQ syntax as follows:
Var nums = from n in data where n <150 select Factorial (n );
Var numsParallel = from n in data. AsParallel () where n <150 select Factorial (n );
Once you use AsParallel (), the subsequent operations will use multi-process processing. AsParallel () returns IParallelEnumerable () instead of IEnumerable (). PLNQ is implemented as a set of extension methods of IParallelEnumerable.
The above example is very simple because there is no shared data and there is no requirement for the order of returned results.
Each parallel query is partitioned first. PLINQ partitions the input elements by quantity and creates and executes the query. Partitioning is one of the most important parts of PLINQ.
First of all, partitions cannot take a lot of time. If you spend too much time on partitions, there will be less time to process data. PLINQ uses four different partitioning algorithms based on the input source and the query type you created. The simplest partition is by range, which is divided by the number of input tasks in sequence.
For example, a 1000-item input sequence will create a 250-item split on a quad-core machine. Range partitioning is used when the query source supports the index sequence and can report the number of items in the sequence. That is to say, the query source by range partition is similar to List , Array, and other support IList The sequence of interfaces.
The second is to partition by block. The internal performance of the algorithm will change over time. When a task requires more work, it will input an item for each task at any time.
The other two partition schemes are specific query operations. One is the stripe partition, And the stripe partition is a special range partition used to optimize the starting element of the processing sequence. Each processing worker thread jumps N projects and then processes M projects, so the loop ends until the end. Another is hash partitioning. Hash partitioning is a special algorithm used to process connection queries, group connection queries, groups, deduplication, union, and intersection. Those time-consuming operations plus specific partition algorithms can achieve higher query parallelization.
Hash partitions ensure that all items that process the same task generate the same hash value.
In addition to partitioning, there are three algorithms used by PLINQ in parallel tasks: Pipeline, stop, start, and reverse enumeration. MPs queue is used by default.
Pipelines are used, and one thread is used to process enumeration. Multiple Threads are used to process each element of the query sequence. In MPs queue mode, how many CPU cores are used.
Stop and start means that the thread that starts enumeration will assume all the threads that run the query expression. This method is used when you use ToList () or ToArray () or PLINQ that returns all results at any time.
As follows:
Var stopAndGoArray = (from n in data. AsParallel ()
Where n <1, 150
Select Factorial (n). ToArray ();
Var stopAndGoList = (from n in data. AsParallel ()
Where n <1, 150
Select Factorial (n). ToList ();
When using the stop and start modes, You Can slightly improve the performance during high memory usage.
Reverse enumeration does not produce a result. It performs some actions on the results of each query expression. For example:
Var nums2 = from n in data. AsParallel ()
Where n <1, 150
Select Factorial (n );
Nums2.ForAll (item => Console. WriteLine (item ));
Reverse enumeration uses less memory than stopping and starting. It is usually the fastest enumeration method.
All LINQ queries are passively executed. They are executed only when you access the query result elements. PLINQ works in a different way. It is more like LINQ to SQL, or an entity framework.
When you access the first element, the entire result sequence is generated.
Parallel Algorithms are limited by Amdahl rules. The acceleration ratio of a multi-processor program is restricted by the continuous running of the program. For more PLINQ content, see MSDN: http://msdn.microsoft.com/zh-cn/library/dd460688 (v = vs.110). aspx

Related Article

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.