浅尝EffectiveCSharp_4

Item 8: 使用查询语法,避免循环 Prefer Query Syntax to Loops

  • 使用循环语句
    int[] foo = new int[100];
    for (int num = 0; num < foo.Length; num++)
    foo[num]
    = num * num;
  • 使用查询语法
    int[] foo = (from n in Enumerable.Range(0, 100) select n * n).ToArray();
  • The .NET BCL has a ForAll implementation in List<T>. It’s just as simple
    to create one for IEnumerable<T>:
    The .NET BCL has a ForAll implementation in List<T>. It’s just as simple to create one for IEnumerable<T>:
    public static class Extensions
    {
      public static void ForAll<T>(this IEnumerable<T> sequence,Action<T> action)
      {
        foreach (T item in sequence)
          action(item);
      }
    }
  • 在默认情形下,查询句法表达式的结果的类型是IEnumerable<T>。IEnumerable<T>接口的一个很好的特征是,实现它们的对象可以把实际的查询运算延迟到开发人员第一次试图对返回值进行迭代时才进行。 LINQ和查询句法表达式利用了这个特性,将查询的实际运算延迟到了你第一次对返回值进行循环时才进行。假如你对IEnumerable<T>的结果从不进行迭代的话,那么查询根本就不会执行。若你不要延迟,可以使用 内置的ToList() 和ToArray() 运算符来返回一个包括了结果集的List<T>或者数组
  • 很多时候,你可能需要进行嵌套操作。假如你要从0到99中产生(X,Y)对。你肯定会使用下面的嵌套类。Many operations require you to work through nested loops. Suppose you need to generate (X,Y) pairs for all integers from 0 through 99. It’s obvious how you would do that with nested loops:
    private static IEnumerable<Tuple<int, int>> ProduceIndices()
    {
      for (int x = 0; x < 100; x++)
      for (int y = 0; y < 100; y++)
      yield return Tuple.Create(x, y);
    }

    当然也可以使用查询语法来返回一个相同的对象

    private static IEnumerable<Tuple<int, int>> QueryIndices()
    {
      return from x in Enumerable.Range(0, 100)
          from y
    in Enumerable.Range(0, 100)
          select Tuple.Create(x, y);
    }
  • 他们看起来很像,但是查询语法当问题变得更加复杂的时候,查询语法依然简易。下面我们来看看产生X,Y和小于100的对。They look similar, but the query syntax keeps its simplicity even as the problem description gets more difficult. Change the problem to generating only those pairs where the sum of X and Y is less than 100. Compare these two methods:
    private static IEnumerable<Tuple<int, int>> ProduceIndices2()
    {
      for (int x = 0; x < 100; x++)
        for (int y = 0; y < 100; y++)
          if (x + y < 100)
            yield return Tuple.Create(x, y);
    }
    private static IEnumerable<Tuple<int, int>> QueryIndices2()
    {
      return from x in Enumerable.Range(0, 100)
          from y
    in Enumerable.Range(0, 100)
          where x + y < 100
          select Tuple.Create(x, y);
    }
    It’s still close, but the imperative syntax starts to hide its meaning under the necessary syntax used to produce the result.
  •  现在,我们再来更改下要求
    private static IEnumerable<Tuple<int, int>> ProduceIndices3()
    {
      var storage
    = new List<Tuple<int, int>>();
      for (int x = 0; x < 100; x++)
        for (int y = 0; y < 100; y++)
          if (x + y < 100)
            storage.Add(Tuple.Create(x, y));
            storage.Sort((point1, point2)
    => 
             (point2.Item1*point2.Item1 +
            point2.Item2
    * point2.Item2).CompareTo(
            point1.Item1
    * point1.Item1 +
            point1.Item2
    * point1.Item2));
            return storage;
    }
    private static IEnumerable<Tuple<int, int>> QueryIndices3()
    {
      return from x in Enumerable.Range(0, 100)
          from y
    in Enumerable.Range(0, 100)
          where x + y < 100
          orderby (x
    *x + y*y) descending
          select Tuple.Create(x, y);
    }
  • Some methods do not have equivalent query syntax. Methods such as Take, TakeWhile, Skip, SkipWhile, Min, and Max require you to use the method syntax at some level.
  • 有人断言查询语法比循环慢很多,事实上并不是这样。This is the part of any discussion where someone usually asserts that queries perform more slowly than other loops. While you can certainly create examples where a hand-coded loop will outperform a query, it’s not a general rule. 你确实需要考虑效率,以确定是否有特定的情况下查询构造不执行不够好。然而,在完全重写算法之前,考虑对LINQ的并行扩展。You do need to measure performance to determine if you have a specific case where the query constructs don’t perform well enough. However, before completely rewriting an algorithm, consider the parallel extensions for LINQ. 另一个优点是,使用查询语法你可以使用AsParallel()方法并行执行这些查询。Another advantage to using query syntax is that you can execute those queries in parallel using the .AsParallel() method.

Item 9: 避免在你的API中进行转换操作 Avoid Conversion Operators in Your APIs

  • 转换操作是一种等代类型(Substitutability)间操作转换操作。等代类型就是指一个类可以取代另一个类。这可能是件好事:一个派生类的对象可以被它基类的一个对象取代,一个经典的例子就是形状继承。先有一个形状类,然后派生出很多其它的类型:长方形,椭圆形,圆形以及其它。你可以在任何地方用图形状来取代圆形,这就是多态的等代类型。这是正确的,因为圆形就是一个特殊的形状。当你创建一个类时,明确的类型转化是可以自动完成的。正如.Net中类的继承,因为System.Object是所有类型的基类,所以任何类型都可以用System.Obejct来取代。同样的情况,你所创建的任何类型,也应该可以用它所实现的接口来取代,或者用它的基类接口来取代,或者就用基类来取代。不仅如此,C#语言还支持很多其它的转换。
  • 使用转换操作应该基于编译时的类型对象,而不是运行时的类型对象。用户可能须要对类型进行多样化的强制转换操作,这样的实际操作可能产生不维护的代码。 Finally, the rules for invoking conversion operators are based on the compile-time type of an object, not the runtime type of an object. Users of your type might need to perform multiple casts to invoke the conversion operators, a practice that leads to unmaintainable code.
  • 你可以使用转换操作把一个未知类型转化为你的类型,这会更加清楚的表现创建新对象的操作。转换操作会在代码中产生难于发现的问题。假设有下面这样的类图
    public class Circle : Shape
    {
      private PointF center;
      private float radius;
      public Circle() :
      this(PointF.Empty, 0)
      {
      }
      public Circle(PointF c, float r)
      {
        center
    = c;
        radius
    = r;
      }
      public override void Draw()
      {
        //...
      }
      static public implicit operator Ellipse(Circle c)//Circle隐式转化为Ellipse
      {
        return new Ellipse(c.center, c.center,c.radius, c.radius);
      }
    }
    现在,你已经有了一个隐式转换操作, 你可以在任何有Ellipse的地方用Circle来转换,因此,转换自动进行
    public static double ComputeArea(Ellipse e)
    {
      // return the area of the ellipse.
      return e.R1 * e.R2 * Math.PI;
    }
    // call it:
    Circle c1 = new Circle(new PointF(3.0f, 0), 5.0f);
    ComputeArea(c1);
    上面的例子就是我对替代性的理解:一个圆可以替换椭圆.
  • public static void Flatten(Ellipse e)
    {
      e.R1
    /= 2;
      e.R2
    *= 2;
    }
    // call it using a circle:
    Circle c = new Circle(new PointF(3.0f, 0), 5.0f);
    Flatten(c);//Flatten( ( Ellipse ) c );这里如果强制转换,原来的问题依然存在。只是把
           //把临时对象进行(flatten)操作后就丢掉了
  • 这是无效的,Flatten()方法得到的参数是从你的转换操作中创建的新的Ellipse对象。这个临时对象被Flatten()函数修改,而且它很快成为垃圾对象。正是因为这个临时对象,Flatten()函数产生了副作用。最后的结果就是这个Circle对象,根本就没有发生任何改变。从隐式转换修改成显示转换也只是强迫用户调用强制转换而以。
    Circle c = new Circle(new PointF(3.0f, 0), 5.0f);
    Flatten(
    new Ellipse(c));
    //如果是这样,也只是把Ellipse传给Flatten之后就丢弃了.因此需要创建一个新的Ellipse实例来保存
    Ellipse e = new Ellipse(c);
    Flatten(e);
  • Conversion operators introduce a form of substitutability that causes problems in your code. You’re indicating that, in all cases, users can reasonably expect that another class can be used in place of the one you created. When this substituted object is accessed, you cause clients to work with temporary objects or internal fields in place of the class you created. You then modify temporary objects and discard the results. These subtle bugs are hard to find because the compiler generates code to convert these objects. Avoid conversion operators in your APIs.
原文地址:https://www.cnblogs.com/TivonStone/p/1730764.html