// Find the first employees: var earlyFolks = from e in allEmployees where e.Classification == EmployeeType.Salary where e.YearsOfService > 20 where e.MonthlySalary < 4000 select e;
// Find the newest people: var newest = from e in allEmployees where e.Classification == EmployeeType.Salary where e.YearsOfService < 20 where e.MonthlySalary < 4000 select e;
你可以将这些 where 合并为一条子句,然而这并不会带来太大区别。查询操作之间本就可以拼接 (见31条),而简单的 where 谓词还会有可能内联 (inline),因此,这两种写法在性能上是一样的。
// else where var allEmployees = FindAllEmployees();
var earlyFolks = from e in allEmployees whereLowPaidSalaried(e) && e.YearsOfService > 20 select e; // Find the newest people: var newest = from e in allEmployees where e.Classification == EmployeeType.Salary where e.YearsOfService < 20 where e.MonthlySalary < 4000 select e;
privatestatic IQueryable<Employee> LowPaidSalariedFilter(this IQueryable<Employee> sequence) => from s in sequence where s.Classification == EmployeeType.Salary && s.MonthlySalary < 4000 select s;
// else where var allEmployees = FindAllEmployees();
// Find the first employees: var salaried = allEmployees.LowPaidSalariedFilter();
var earlyFolks = salaried.Where(e => e.YearsOfService > 20);
// Find the newest people: var newest = salaried.Where(e => e.YearsOfService < 2);
///<summary> /// 0123456789 ///</summary> privatestaticvoidLazyEvaluation3() { var answers = from number inAllNumbers() select number; var smallNumbers = answers.Take(10);
foreach (int num in smallNumbers) Console.Write(num); }
privatestatic IEnumerable<int> AllNumbers() { var number = 0;
while (number < int.MaxValue) yieldreturn number++; }
此示例不必生成整个序列,而是仅生成十个数 (虽然 AllNumbers() 可以生成至 int.MaxValue)。Take() 方法只需要其中的前 N 个对象。
反之,如果把查询语句改成这样,程序将一直运行,直到 int.MaxValue才停下:
1 2 3 4
var answers = from number inAllNumbers() where number < 10 select number;
/** * no if statement in this method * use shift operators * if positive num : num >> 31 == 0 * if negative num : num >> 31 == -1 * ~0 = -1 (ffffffff) * ~-1 = 0 (0) */ publicstaticvoidsumAvoidBranchPrediction(int[] array, int arraySize) { longstart= System.nanoTime(); longsum=0;
for (inti=0; i < arraySize; ++i) { for (intj=0; j < arraySize; ++j) { sum += ~((array[j] - 128) >> 31) & array[j]; } } System.out.println(System.nanoTime() - start + " ns"); // _606_267_300 ns System.out.println("sum : " + sum); }
// Variant type parameters could be declared in interfaces or delegates only!
interfaceICovariant<outT> { }
classCovariant<T> : ICovariant<T> { }
interfaceIContravariant<inT> { }
classContravariant<T> : IContravariant<T> { }
interfaceIInvariant<T> { }
classInvariant<T> : IInvariant<T> { }
classProgram { privatestaticvoidCovariant(/* out */) { ICovariant<object> obj = new Covariant<object>(); ICovariant<string> str = new Covariant<string>();
// You can assign "Derived" to "Base" obj = str; str = obj; // error }
privatestaticvoidContravariant(/* in */) { IContravariant<object> obj = new Contravariant<object>(); IContravariant<string> str = new Contravariant<string>();
// You can assign "Base" to "Derived" str = obj; obj = str; // error }
privatestaticvoidInvariant(/* none */) { IInvariant<object> obj = new Invariant<object>(); IInvariant<string> str = new Invariant<string>();
// You can't do any assign obj = str; // error str = obj; // error } }
Java 用起来如此舒适的一个因素在于,它是一门安全的语言 (safe language)。这意味着,它对于缓冲区溢出、数组越界、非法指针以及其他的内存破坏错误都自动免疫,而这些错误却困扰着诸如 C 和 C++ 这样的不安全语言。在一门安全语言中,在设计类时,无论系统的其他部分发生什么问题,类的约束都可以保持为真。对于那些 “把所有内存当作一个巨大的数组来对待” 的语言来说,这是不可能的。
即使在安全的语言中,如果不采取一点措施,还是无法与其它的类隔离开来。建设类的客户端会尽其所能来破坏这个类的约束条件,因此你必须保护性地设计程序。实际上,只有当有人试图破坏系统的安全性时,才可能发生这种情况;更有可能的是,对你的 API 产生误解的程序员,所导致的各种不可预期的行为,只好由类来处理。无论是何种情况,编写面对客户端的不良行为仍保持健壮性的类,这是非常值得投入时间去做的事情。
// Broken "immutable" time period class publicclassPeriod { privatefinal Date start; privatefinal Date end;
publicPeriod(Date start, Date end) { if (start.compareTo(end) > 0) thrownewIllegalArgumentException(start + " after " + end); this.start = start; this.end = end; }
public Date start() { return start; }
public Date end() { return end; } }
乍看之下,此类似乎是不可变的,并且加了周期的起始时间 (start) 不能在结束时间 (end) 之后。然而,因为 Date 类本身是可变的,因此很容易违反这个约束条件:
1 2 3 4 5
// Attack the internals of a Period instance Datestart=newDate(); Dateend=newDate(); Periodp=newPeriod(start, end); end.setYear(78); // Modifies internals of p!
可以肯定地说,只要有可能,都应该使用不可变的对象作为对象内部的组件,这样就不必再为保护性拷贝(见第15条)操心。在前面的 Period 例子中,有经验的程序员通常使用 Date.getTime() 返回的 long 基本类型作为内部的时间表示法, 而不是使用 Date 对象引用。他们之所以这样做,主要因为 Date 是可变的。
var output = $@"The First five items are: { src.Take( 5 ).Select( n => $@"Item: {n.ToString()}" ).Aggregate( (c, a) => $@"{Environment.NewLine}{a}" ) }";
上面的这种写法可能不太会出现在正式的产品代码中,但可以看出,内插字符串和 C# 之间结合的相当密切。ASP.NET MVC 框架中的 Razor View 引擎也支持内插字符串,使得开发者在编写 Web 应用程序时能够更便捷地以 HTML 的形式来输出信息。默认的 MVC 应用程序本身就演示了怎样在 Razor View 中使用内插字符串,以下示例节选自 controller 部分,它可以显示当前登入的用户名: