第5章 基本语言特性 — 精通MVC 3 框架

 

Essential Language Features
基本语言特性

 

C# is a feature-rich language, and not all programmers are familiar with all of the features we will rely on in this book. In this chapter, we are going to look at the C# language features that a good MVC programmer needs to know.
C#是一种特性丰富的语言,而且,我们这本书所依赖的所有特性,并不是每个程序员都熟悉的。本章中,我们打算考察一个良好的MVC程序员所需要了解的C#语言特性。

We provide only a short summary of each feature. If you want more in-depth coverage of C# or LINQ, two of Adam’s books may be of interest. For a complete guide to C#, try Introducing Visual C# 2010; and for in-depth coverage of LINQ, check out Pro LINQ in C# 2010 (written with Joe Rattz); both books are published by Apress.
我们只对每个特性提供简短的概要。如果你想深度了解C#LINQ,可参考Adam的另两本书。对C#的完整指南可参阅“Introducing Visual C# 2010一书,要深度涉及LINQ,请查阅“Pro LINQ in C# 2010 (与Joe Rattz合著),这两本书都由Apress出版。

If you are an experienced C# programmer, you can skip the first part of this chapter. But you won’t want to miss the second part, in which we provide a tour of Razor, the new view engine introduced with MVC 3. The syntax for Razor is different from the default ASPX engine that previous versions of MVC have relied on and which is still used in ASP.NET Web Forms. Razor coexists alongside the ASPX engine, which can still be used for MVC 3 projects, but we have adopted Razor throughout this book. Even if you want to use the old <% and %> markup in your own projects, reading the Razor section in this chapter will help you follow the examples we present in this book.
如果你是一个有经验的C#程序员,你可以跳过本章的第一部分。但请不要错过第二部分,这里我们提供了Razor的一个教程,这是MVC 3的一个新的视图引擎。Razor的语法不同于以前的MVC所依赖的默认的ASPX引擎,它仍然可以用于MVC 3项目,但我们这本书已采用了Razor。即使你希望在你的项目中使用旧式的<%%>标记,阅读本章的Razor章节也会有助于你跟随我们本书所表现的例子。

Essential C# Features
C#基本特性

We are going to start by looking at some C# language features that you’ll need to understand in order to fully follow the examples presented in the rest of the book. You don’t need to re-create the examples in this part of the chapter to be able to follow along, but if you do want to, use the Visual Studio Console Application project template; this allows you to use the System.Console class to write output to a command window.
我们从考察一些C#语言特性开始,这些是你需要了解的,以使你能够理解本书其余部分的示例。你不需要重新生成本章这部分的示例,但你如果想这么做,请使用Visual Studio的“控制台应用程序”项目模板,这可以让你能够使用System.Console类把输出写到命令行窗口。

Using Automatically Implemented Properties
自动属性

The C# property feature lets you expose a piece of data from a class in a way that decouples the data from how it is set and retrieved. Listing 5-1 contains a simple example in a class called Product.
C#属性特性使你能够暴露一个类的一片数据,这些数据与如何设置和接收数据采取一种松耦合的方式。清单5-1是一个叫做Product类的例子。

Listing 5-1. Defining a Property

public class Product {

    private string name;

 

    public string Name {

        get { return name; }

        set { name = value; }

    }

}

The property, called Name, is shown in bold. The statements in the get code block (known as the getter) are performed when the value of the property is read, and the statements in the set code block are performed when a value is assigned to the property (the special variable value represents the assigned value). A property is consumed by other classes as though it were a field, as shown in Listing 5-2.
名为Name的属性以黑体表示。在get代码块(称为getter)中的语句在读取该属性值时执行,而set代码块中的语句在把一个值设置给这个属性时执行(特殊变量value表示要赋的值)。属性是由其它类来使用的,就好象它是一个字段一样,如清单5-2所示。

Listing 5-2. Consuming a Property

using System;

class Program {

    static void Main(string[] args) {

        // create a new Product object

        Product myProduct = new Product();

        // set the property value

        myProduct.Name = "Kayak";

        // get the property

        string productName = myProduct.Name;

        Console.WriteLine("Product name: {0}", productName);

    }

}

You can see that the property value is read and set just like a regular field. Using properties is preferable to using fields because you can change the statements in the get and set blocks without needing to change all the classes that depend on the property. All well and good, except that it gets tedious when you have a class that has a lot of properties, and all the getters and setters do the same thing—mediate access to a field. We end up with something that is needlessly verbose, as shown in Listing 5-3.
你可以看出,属性值的读取和设置就像对一个规则的字段进行操作一样。使用属性更可取的是使用字段,因为你可以修改getset块中的语句,而不需要修改依赖于这个属性的整个类。这些都很好,除了当一个类有很多属性时,事情就有点乏味,所有的gettersetter都在做同样的事情 填充字段。如清单5-3所示,对一些不必要的冗长的事情,我们应该终止这种状况。

Listing 5-3. Verbose Property Definitions

public class Product {

    private int productID;

    private string name;

    private string description;

    private decimal price;

private string category;

 

    public int ProductID {

        get { return productID; }

        set { productID = value; }

}

 

    public string Name {

        get { return name; }

        set { name = value; }

}

 

    public string Description {

        get { return description; }

        set { description = value; }

}

 

    ...and so on...

}

We want the flexibility of properties, but we don’t need custom getters and setters at the moment. The solution is an automatically implemented property, also known as an automatic property. With an automatic property, you can create the pattern of a field-backed property, without defining the field or specifying the code in the getter and setter, as Listing 5-4 shows.
我们希望属性有适应性,但我们此刻不需要自定义的gettersetter。解决方案是一种自动实现的属性,也称为自动属性。利用自动属性,你可以生成一个字段支持的属性模式,而不需要定义这个字段或在gettersetter中指定代码,如清单5-4所示。

Listing 5-4. Using Automatically Implemented Properties

public class Product {

    public int ProductID { get; set; }

    public string Name { get; set;}

    public string Description { get; set;}

    public decimal Price { get; set; }

    public string Category { set; get;}

}

There are a couple of key points to note when using automatic properties. The first is that we don’t define the bodies of the getter and setter. The second is that we don’t define the field that the property is backed by. Both of these are done for us by the C# compiler when we build our class. Using an automatic property is no different from using a regular property; the code in Listing 5-2 will still work. By using automatic properties, we save ourselves some typing, create code that is easier to read, and still preserve the flexibility that a property provides. If the day comes when we need to change the way a property is implemented, we can then return to the regular property format. Let’s imagine we need to change the way the Name property is composed, as shown in Listing 5-5.
在使用自动属性时,有两个关键点要注意。第一是我们不定义gettersetter的体。第二是我们不定义由该属性返回的字段。这两者都由C#编译器在我们建立这个类时自动完成。使用自动属性与使用规则属性没什么不同,清单5-2所列出的代码仍会正常工作。通过使用自动属性,我们减少了一些键入,形成的代码更易于阅读,并且仍然保持了属性的灵活性。如果有一天我们需要改变一个属性实现的方式,我们还可以返回到规则属性的格式。让我们假设我们需要修改构成Name属性的构成方式,如清单5-5所示。

Listing 5-5. Reverting from an Automatic to a Regular Property

public class Product {

    private string name;

    public int ProductID { get; set; }

 

    public string Name {

        get { return ProductID + name;}

        set { name = value; }

    }

    public string Description { get; set;}

    public decimal Price { get; set; }

    public string Category { set; get;}

}

n Note Notice that we must implement both the getter and setter to return to a regular property. C# doesn’t support mixing automatic- and regular-style getters and setters.
注:注意,我们必须同时实现gettersetter来回到规则属性的定义方式。C#不支持混用自动和规则风格的gettersetter

Using Object and Collection Initializers
对象与集合的初始化器

Another tiresome programming task is constructing a new object and then assigning values to the properties, as shown in Listing 5-6.
另一个烦人的编程任务是构造一个新的对象,然后给属性赋值,如清单5-6

Listing 5-6. Constructing and Initializing an Object with Properties

using System;

class Program {

   static void Main(string[] args) {

      // create a new Product object

      Product myProduct = new Product();

      // set the property values

      myProduct.ProductID = 100;

      myProduct.Name = "Kayak";

      myProduct.Description = "A boat for one person";

      myProduct.Price = 275M;

      myProduct.Category = "Watersports";

      // process the product

      ProcessProduct(myProduct);

   }

   private static void ProcessProduct(Product prodParam) {

      //...statements to process product in some way

   }

}

We must go through three stages to create a Product object and pass it to the ProcessProduct method: create the object, set the parameter values, and then call the method. Fortunately, we can use the object initializer feature, which allows us to do everything in one go, as shown in Listing 5-7.
我们必须通过三个步骤来生成一个Product对象,并把它传递给ProcessProduct方法:生成对象、设置参数值、然后调用该方法。幸运的是,我们可以用对象初始化特性,它允许我们一步完成所有的事情,如清单5-7所示。

Listing 5-7. Using the Object Initializer Feature

class Program {

    static void Main(string[] args) {

        // create a new Product object

        ProcessProduct(new Product {

            ProductID = 100, Name = "Kayak",

            Description = "A boat for one person",

            Price = 275M, Category = "Watersports"

        });

    }

    private static void ProcessProduct(Product prodParam) {

        //...statements to process product in some way

    }

}

The braces ({}) after the call to the Product constructor are the initializer. We can supply values to the parameters as part of the construction process. The result is an instance of the Product class that we can pass directly to the ProcessProduct method, which means we don’t need to use a local variable to refer to the Product while we initialize it. The same feature lets us initialize the contents of collections and arrays as part of the construction process, as demonstrated by Listing 5-8.
调用Product构造函数之后的花括号({})就是初始化器。我们可以把值提供给参数作为构造过程的一部分。结果是Product类的一个实例,我们把它直接传递给ProcessProduct方法。这样,当我们要对Product进行初始化时,我们不需要用一个局部变量指向Product。用同样的特性,我们可以初始化集合和数组的内容作为构造过程的一部分,如5-8所示。

Listing 5-8. Initializing Collections and Arrays

using System.Collections.Generic;

class Program {

   static void Main(string[] args) {

      string[] stringArray = { "apple", "orange", "plum" };

      List<int> intList = new List<int> { 10, 20, 30, 40 };

      Dictionary<string, int> myDict = new Dictionary<string, int> {

         { "apple", 10 },

         { "orange", 20 },

         { "plum", 30 }

      };

   }

}

 Listing 5-8 demonstrates how to construct and initialize an array and two classes from the generic collection library. This feature is a syntax convenience—it just makes C# more pleasant to use and doesn’t have any other impact or benefit.
清单5-8演示了如何构造并初始化一个数组和两个一般集合库中的类。这种特性是一种语法惯例 它只是使C#用起来更舒适并没有任何其它影响或好处。

Using Extension Methods
扩展方法

Extension methods are a convenient way of adding methods to classes that you don’t own and so can’t modify directly. Listing 5-9 shows the ShoppingCart class, which represents a collection of Products.
扩展方法是给那些不是你拥有因而不能直接修改的类添加方法的一种方便的办法。清单5-9演示了ShoppingCart类,它表示了一个Products集合。

Listing 5-9. The ShoppingCart Class

using System.Collections.Generic;

public class ShoppingCart {

    public List<Product> Products { get; set; }

}

This is a very simple class that acts as a wrapper around a List of Product objects (we only need a basic class for this example). Suppose we need to be able to determine the total value of the Product objects in the ShoppingCart class, but we can’t modify the class itself, perhaps because it comes from a third party and we don’t have the source code. Fortunately, we can use an extension method to get the functionality we need, as shown in Listing 5-10.
这是一个很简单的类,它的作用是封装一个Product对象的列表(对这个例子我们只需要一个基础类Product)。假设我们需要能够确定这个ShoppingCart类中Product对象的总值,但我们不能修改这个类本身,这也许因为它来自于第三方,而我们没有其源代码。幸运的是,我们可以用一个扩展方法来获得我们所需要的功能,如清单5-10所示。

Listing 5-10. Defining an Extension Method

public static class MyExtensionMethods {

    public static decimal TotalPrices(this ShoppingCart cartParam) {

        decimal total = 0;

        foreach (Product prod in cartParam.Products) {

            total += prod.Price;

        }

        return total;

    }

}

The this keyword in front of the first parameter marks TotalPrices as an extension method. The first parameter tells .NET which class the extension method can be applied to—ShoppingCart in our case. We can refer to the instance of the ShoppingCart that the extension method has been applied to by using the cartParam parameter. Our method enumerates through the Products in the ShoppingCart and returns the sum of the Product.Price property. Listing 5-11 shows how we apply an extension method.
第一个参数前的this关键字把TotalPrices标记为一个扩展方法。第一个参数告诉.NET,这个扩展方法运用于哪个类 在我们的例子中是ShoppingCart。我们可以通过使用cartParam参数作为这个类ShoppingCart的实例。这个扩展方法枚举了ShoppingCart中的Products,并返回Product.Price属性的总和。清单5-11给我们演示了如何运用一个扩展方法。

n Note Extension methods don’t let you break through the access rules that classes define for their methods, fields, and properties. You can extend the functionality of a class by using an extension method, but using only the class members that you had access to anyway.
注:扩展方法并不让你突破类为它的方法、字段、和属性定义的访问规则。你可以通过使用一个扩展方法来扩展一个类的功能,但只能使用你有权访问的类成员。

Listing 5-11. Applying an Extension Method

using System;

using System.Collections.Generic;

class Program {

    static void Main(string[] args) {

        // create and populate ShoppingCart

        ShoppingCart cart = new ShoppingCart {

            Products = new List<Product> {

                new Product {Name = "Kayak", Price = 275M},

                new Product {Name = "Lifejacket", Price = 48.95M},

                new Product {Name = "Soccer ball", Price = 19.50M},

                new Product {Name = "Corner flag", Price = 34.95M}

            }

        };

        // get the total value of the products in the cart

        decimal cartTotal = cart.TotalPrices();

        Console.WriteLine("Total: {0:c}", cartTotal);

    }

}

Listing 5-11 creates a ShoppingCart and populates it with Product objects using the object initializer feature. The statement that applies the extension method is shown in bold. As you can see, we just call the method as though it were a part of the ShoppingCart class. Notice also that the extension method wasn’t defined in the same class in which we used it. .NET will find your extension classes if they are in the scope of the current class, meaning that they are part of the same namespace or in a namespace that is the subject of a using statement. Here’s the output from the class in Listing 5-11:
清单5-11生成了一个ShoppingCart,并用对象初始化特性给它填充了一些Product对象。运用扩展方法的语句以黑体显示。正如你所看到的,我们只是调用了这个方法,就好像它是ShoppingCart类的一部分。也要注意到,扩展方法并不是在我们使用它的同一个类中定义的。如果它们位于当前类的范围,.NET会找到你的扩展类,即,它们是同一命名空间的一部分,或是在using语句子项的命名空间中。以下是清单5-11中类的输出:

Total: $378.40

Applying Extension Methods to an Interface
把扩展方法用于一个接口

We can also create extension methods that apply to an interface, which allows us to call the extension method on all of the classes that implement the interface. Listing 5-12 shows the ShoppingCart class updated to implement the IEnumerable<Product> interface.
我们也可以生成一个运用于一个接口的扩展方法,它允许我们在实现这个接口的所有类上调用这个扩展方法。清单5-12演示了经过修改的、实现了IEnumerable<Product>接口的ShoppingCart类。

Listing 5-12. Implementing an Interface in the ShoppingCart Class

using System.Collections;

using System.Collections.Generic;

public class ShoppingCart : IEnumerable<Product> {

    public List<Product> Products { get; set; }

    public IEnumerator<Product> GetEnumerator() {

        return Products.GetEnumerator();

    }

    IEnumerator IEnumerable.GetEnumerator() {

        return GetEnumerator();

    }

}

We can now update our extension method so that it deals with IEnumerable<Product>, as shown in Listing 5-13.
现在我们可以更新我们的扩展方法,以使它处理IEnumerable<Product>,如清单5-13所示。

Listing 5-13. An Extension Method That Works on an Interface

using System.Collections.Generic;

public static class MyExtensionMethods {

    public static decimal TotalPrices(this IEnumerable<Product> productEnum) {

        decimal total = 0;

        foreach (Product prod in productEnum) {

            total += prod.Price;

        }

        return total;

    }

}

The first parameter type has changed to IEnumerable<Product>, which means that the foreach loop in the method body works directly on the parameter object. Otherwise, the extension method is unchanged. The switch to the interface means that we can calculate the total value of the Products enumerated by any IEnumerable<Product>, which includes instances of ShoppingCart but also arrays of Products, as shown in Listing 5-14.
第一个参数类型改成为IEnumerable<Product>,它可以使方法体中的foreach循环直接对参数对象进行操作。在其它方面,该扩展方法未作改变。切换成接口意味着我们可以对任何IEnumerable<Product>所枚举的Products进行总价计算,IEnumerable<Product>不仅包括了ShoppingCart实例,而且也可以是Products数组(因为数组可以实现IEnumerable<T>接口,见稍后的“注” 译者注),如清单5-14所示。

Listing 5-14. Applying an Extension Method to Different Implementations of the Same Interface

using System;

using System.Collections.Generic;

class Program {

    static void Main(string[] args) {

        // create and populate ShoppingCart

        IEnumerable<Product> products = new ShoppingCart {

            Products = new List<Product> {

                new Product {Name = "Kayak", Price = 275M},

                new Product {Name = "Lifejacket", Price = 48.95M},

                new Product {Name = "Soccer ball", Price = 19.50M},

                new Product {Name = "Corner flag", Price = 34.95M}

            }

        };

        // create and populate an array of Product objects

        Product[] productArray = {

            new Product {Name = "Kayak", Price = 275M},

            new Product {Name = "Lifejacket", Price = 48.95M},

            new Product {Name = "Soccer ball", Price = 19.50M},

            new Product {Name = "Corner flag", Price = 34.95M}

        };

        // get the total value of the products in the cart

        decimal cartTotal = products.TotalPrices();

        // 注:这里有误,应该用:productArray.TotalPrices() 译者

        decimal arrayTotal = products.TotalPrices();

        Console.WriteLine("Cart Total: {0:c}", cartTotal);

        Console.WriteLine("Array Total: {0:c}", arrayTotal);

    }

}

n Note The way that C# arrays implement the IEnumerable<T> interface is a little odd. You won’t find it included in the list of implemented interfaces in the MSDN documentation. The support is handled by the compiler so that code for earlier versions C# will still compile. Odd, but true. We could have used another generic collection class in this example, but we wanted to show off our knowledge of the dark corners of the C# specification. Also odd, but true.
注:C#数组实现IEnumerable<T>接口的方法有点古怪。你会发现MSDN文档中已实现的接口列表中没有它。这个支持是由编译器处理的,于是对较早版本的C#代码将仍然可以编译。偶尔,但确实。我们可以在这个例子中使用另一种一般集合类,但我们想炫耀一下C#规范也未涉及到的知识。也是偶尔,但确实。

If you compile and run the class in Listing 5-14, you will see the following results, which demonstrate that we get the same result from the extension method, irrespective of how the Product objects are collected:
如果你编译并运行清单5-14中的类,你会看到以下结果,它演示了我们通过扩展方法得到的同样结果,而不管Product对象是如何集合的。

Cart Total: $378.40

Array Total: $378.40

Creating Filtering Extension Methods
生成过滤扩展方法

The last thing we want to show you about extension methods is that they can be used to filter collections of objects. An extension method that operates on an IEnumerable<T> and that also returns an IEnumerable<T> can use the yield keyword to apply selection criteria to items in the source data to produce a reduced set of results. Listing 5-15 demonstrates such a method.
我们想向你演示扩展方法的最后一件事是它们可以用于过滤对象的集合。一个对IEnumerable<T>操作且也返回一个IEnumerable<T>结果的扩展方法可以用yield关键词把选择条件运用于源数据的条目,以产生一个结果子集。清单5-15演示了这种方法。

Listing 5-15. A Filtering Extension Method

public static IEnumerable<Product> FilterByCategory(

        this IEnumerable<Product> productEnum, string categoryParam) {

    foreach (Product prod in productEnum) {

        if (prod.Category == categoryParam) {

            yield return prod;

        }

    }

}

This extension method, called FilterByCategory, takes an additional parameter that allows us to inject a filter condition when we call the method. Those Product objects whose Category property matches the parameter are returned in the result IEnumerable<Product> and those that don’t match are discarded. Listing 5-16 shows this method being used.
这个叫做FilterByCategory的扩展方法采用了一个附加的参数,这允许我们在调用这个方法时注入一个过滤条件。Product对象的Category属性与这个参数匹配的那些Product对象以IEnumerable<Product>形式被返回,而不匹配的被丢弃。清单5-16演示了这个方法的使用。

Listing 5-16. Using the Filtering Extension Method

using System;

using System.Collections.Generic;

class Program {

    static void Main(string[] args) {

        // create and populate ShoppingCart

        IEnumerable<Product> products = new ShoppingCart {

            Products = new List<Product> {

                new Product {Name = "Kayak", Category = "Watersports", Price = 275M},

                new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},

               new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},

                new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}

            }

        };

        foreach (Product prod in products.FilterByCategory("Soccer")) {

            Console.WriteLine("Name: {0}, Price {1:c}", prod.Name, prod.Price);

        }

    }

}

When we call the FilterByCategory method on the ShoppingCart, only those Products in the Soccer category are returned. If we compile and run this method, we get the following results:
当我们调用ShoppingCart上的FilterByCategory方法时,只有Soccer分类中的那些Products被返回。如果我们编译并运行这个方法,我们会得到以下结果:

Name: Soccer ball, Price $19.50

Name: Corner flag, Price $34.95

And, of course, we can use extension methods just like any other methods and chain them together. For example, we can filter for the Product objects in the Soccer category, and then pass the results to the TotalPrices method to add up the value of the Price properties, as shown in Listing 5-17.
当然,我们可以像使用其它方法一样使用扩展方法,并可以把它们链接在一起。例如,我们可以过滤Soccer分类目录中的Product,然后把结果传递给TotalPrices方法来加和Price属性的值,如清单5-17所示。

Listing 5-17. Chaining Extension Methods Together

...

decimal total = products.FilterByCategory("Soccer").TotalPrices();

Console.WriteLine("Filtered total: {0:c}", total);

...

The following is the result of these statements:
以下是这些语句的结果:

Filtered total: $54.45

Using Lambda Expressions
Lambda表达式

We can use a delegate to make our FilterByCategory method more general. That way, the delegate that will be invoked against each Product can filter the objects in any way we choose, as shown in Listing 5-18.
我们可以用一个委派使我们的FilterByCategory方法更通用。这样,针对每个Product进行调用的委派可以以我们所选的任何方式来过滤对象,如清单5-18所示。

Listing 5-18. Using a Delegate in an Extension Method

public static IEnumerable<Product> Filter(

        this IEnumerable<Product> productEnum,

        Func<Product, bool> selectorParam) {

    foreach (Product prod in productEnum) {

        if (selectorParam(prod)) {

            yield return prod;

        }

    }

}

We’ve used a Func as the filtering parameter, which means that we don’t need to define the delegate as a type. The delegate takes a Product parameter and returns a bool, which will be true if that Product should be included in the results. The other end of this arrangement is a little verbose, as shown in Listing 5-19.
我们用了一个Func作为过滤参数,意即我们不需要把这个委派定义为一个类型。该委派有一个Product参数,并返回一个布尔型,如果Product应该被包含在结果中则返回true。这段程序的使用有点冗长,如清单5-19所示。

Listing 5-19. Using the Filtering Extension Method with a Func

using System;

using System.Collections.Generic;

class Program {

    static void Main(string[] args) {

        // create and populate ShoppingCart

        IEnumerable<Product> products = new ShoppingCart {

            Products = new List<Product> {

                new Product {Name = "Kayak", Category = "Watersports", Price = 275M},

                new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},

                new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},

                new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}

            }

        };

        Func<Product, bool> categoryFilter = delegate(Product prod) {

            return prod.Category == "Soccer";

        };

        IEnumerable<Product> filteredProducts = products.Filter(categoryFilter);

        foreach (Product prod in filteredProducts) {

            Console.WriteLine("Name: {0}, Price: {1:c}", prod.Name, prod.Price);

        }

    }

}

We took a step forward, in the sense that we can now filter the Product object using any criteria specified in the delegate, but we must define a Func for each criterion we want, which isn’t ideal. The less verbose alternative is to use a lambda expression, which is a concise format for expressing a method body in a delegate. We can use it to replace our delegate definition, as shown in Listing 5-20.
我们向前跨了一步,在当前情形下,现在我们可以用委派中指定的任何条件来过滤Product对象了,但我们必须为我们希望的每个条件定义一个Func,这是不理想的。比较简洁的替代品是用lambda表达式,它是以委派的方式表示一个方法体的一种简洁格式。我们可以用它来替换我们的委派定义,如清单5-20所示。

Listing 5-20. Using a Lambda Expression to Replace a Delegate Definition

Func<Product, bool> categoryFilter = prod => prod.Category == "Soccer";

IEnumerable<Product> filteredProducts = products.Filter(categoryFilter);

The lambda expression is shown in bold. The parameter is expressed without specifying a type, which will be inferred automatically. The => characters are read aloud as “goes to” and links the parameter to the result of the lambda expression. In our example, a Product parameter called prod goes to a bool result, which will be true if the Category parameter of prod is equal to Soccer.
lambda表达式显示为黑体。参数用不指定类型的方式表示,由自动推理去完成。=>字符读作为“转给”,并把这个参数连接到lambda表达式的结果。在上例中,名为prodProduct参数转给一个布尔结果,如果prodCategory参数等于Soccer,将返回true

We can make our syntax even tighter by doing away with the Func entirely, as shown in Listing 5-21.
我们甚至可以完全去掉Func,形成更紧凑的语法,如清单5-21所示。

Listing 5-21. A Lambda Expression Without a Func

IEnumerable<Product> filteredProducts = products.Filter(prod => prod.Category == "Soccer");

In this example, we have supplied the lambda expression as the parameter to the Filter method. This is a nice and natural way of expressing the filter we want to apply. We can combine multiple filters by extending the result part of the lambda expression, as shown in Listing 5-22.
在这个例子中,我们以lambda表达式作为传递给Filter方法的参数。这是我们想运用过滤表达式的一种好而自然的方式。我们可以通过扩展lambda表达式结果部分的方式组合多个过滤,如清单5-22所示。

Listing 5-22. Extending the Filtering Expressed by the Lambda Expression

IEnumerable<Product> filteredProducts = products.Filter(prod =>

      prod.Category == "Soccer" || prod.Price > 20);

OTHER FORMS FOR LAMBDA EXPRESSIONS
LAMBDA表达式的其它形式

We don’t need to express the logic of our delegate in the lambda expression. We can as easily call a method, like this:
我们在lambda表达式中不需要表示委派的逻辑。我们可以很容易地调用一个方法,像这样:

prod => EvaluateProduct(prod)

If we need a lambda expression for a delegate that has multiple parameters, we must wrap the parameters in parentheses, like this:
如果我们需要一个lambda表达式来表示一个多参数的委派,我们必须把参数封装在括号内,像这样:

(prod, count) => prod.Price > 20 && count > 0

And finally, if we need logic in the lambda expression that requires more than one statement, we can do so by using braces ({}) and finishing with a return statement, like this:
最后,如果lambda表达式中的逻辑需要多条语句,我们可以用花括号({})并以一条返回语句来做这件事,像这样:

(prod, count) => {

   //...multiple code statements

   return result;

}

You don’t need to use lambda expressions in your code, but they are a neat way of expressing complex functions simply and in a manner that is readable and clear. We like them a lot, and you’ll see them used liberally throughout this book.
你不一定必须在你的代码中用lambda表达式,但它们是表示复杂功能的一种简洁方法,简单、可读且清晰。我们很喜欢它,而且你也会在本书中看到它们的大量运用。

Using Automatic Type Inference
自动类型接口

The C# var keyword allows you to define a local variable without explicitly specifying the variable type, as demonstrated by Listing 5-23. This is called type inference, or implicit typing.
C#var关键词允许你定义一个局部变量而不明确地指定这个变量的类型,如清单5-23所示。这称为类型接口或隐含分类。

Listing 5-23. Using Type Inference

var myVariable = new Product { Name = "Kayak", Category = "Watersports", Price = 275M };

string name = myVariable.Name; // legal

int count = myVariable.Count; // compiler error

 It is not that myVariable doesn’t have a type. It is just that we are asking the compiler to infer it from the code. You can see from the statements that follow that the compiler will allow only members of the inferred class—Product in this case—to be called.
并不是myVariable没有类型。只是我们要求编译器通过代码来推断它。你可以通过随后的语句看到,编译器将只允许调用推断类 这里是Product 的成员。

Using Anonymous Types
匿名类型

By combining object initializers and type inference, we can create simple data-storage objects without needing to define the corresponding class or struct. Listing 5-24 shows an example.
通过结合对象初始化器和类型接口,我们可以生成一个简单的数据存储对象,而不需要定义相应的类或结构。清单5-24演示了一个例子。

Listing 5-24. Creating an Anonymous Type

var myAnonType = new {

    Name = "MVC",

    Category = "Pattern"

};

Console.WriteLine("Name: {0}, Type: {1}", myAnonType.Name, myAnonType.Category);

In this example, myAnonType is an anonymously typed object. This doesn’t mean that it’s dynamic in the sense that JavaScript variables are dynamically typed. It just means that the type definition will be created automatically by the compiler. Strong typing is still enforced. You can get and set only the properties that have been defined in the initializer, for example.
在这个例子中,myAnonType是一个匿名类型对象。这并不意味着它是动态的,JavaScript变量在这种情况下才是动态类型的。这只表示类型定义由编译器自动生成。强类型仍然是必须的。例如,你只可以获取和设置初始化器中已经定义的那些属性。

The C# compiler generates the class based on the name and type of the parameters in the initializer. Two anonymously typed objects that have the same property names and types will be assigned to the same automatically generated class. This means we can create arrays of anonymously typed objects, as shown in Listing 5-25.
C#编译器基于初始化器中参数的名字和类型来生成这个类。有同样属性名和类型的两个匿名类型对象将被指派为自动生成的同样的类。意即,我们可以生成匿名类型对象的数组,如清单5-25所示。

Listing 5-25. Creating an Array of Anonymously Typed Objects

var oddsAndEnds = new[] {

    new { Name = "MVC", Category = "Pattern"},

    new { Name = "Hat", Category = "Clothing"},

    new { Name = "Apple", Category = "Fruit"}

};

foreach (var item in oddsAndEnds) {

    Console.WriteLine("Name: {0}", item.Name);

}

Notice that we use var to declare the variable array. We must do this because we don’t have a type to specify, as we would in a regularly typed array. Even though we have not defined a class for any of these objects, we can still enumerate the contents of the array and read the value of the Name property from each of them. This is important, because without this feature, we wouldn’t be able to create arrays of anonymously typed objects at all. Or, rather, we could create the arrays, but we wouldn’t be able to do anything useful with them.
注意,我们用var来声明变量数组。我们必须这么做,因为我们没有指定类型,就像在规则类型数组中那。即使我们没对这些对象定义一个类,我们仍然能够枚举这个数组的内容并从每个条目中读取Name属性的值。这是重要的,因为没有这个特性,我们将根本不能生成匿名对象的数组。或者更确切地说,我们能够生成这个数组,但我们将不能用它们做任何有用的事情。

Performing Language Integrated Queries
执行集成语言查询

All of the features we’ve described so far are put to good use in the LINQ feature. We love LINQ. It is a wonderful and strangely compelling addition to .NET. If you’ve never used LINQ, you’ve been missing out. LINQ is a SQL-like syntax for querying data in classes. Imagine that we have a collection of Product objects, and we want to find the three with the highest prices, and print out their names and prices. Without LINQ, we would end up with something similar to Listing 5-26.
到目前为止,我们所描述的所有语言特性都可以很好地用在LINQ特性中。我们热爱LINQ。它是一种奇妙而奇怪地强制添加到.NET的功能。如果你还没用过LINQ,你已经是一个局外人了。LINQ是在类中查询数据的一种类似于SQL的语法。假设我们有一个Product对象的集合,而我们想找到三个最高价,并打印出它们的名字和价格。没有LINQ,我们需用类似清单5-26所示的方法来完成。

Listing 5-26. Querying Without LINQ

using System;

using System.Collections.Generic;

class Program {

    static void Main(string[] args) {

        Product[] products = {

            new Product {Name = "Kayak", Category = "Watersports", Price = 275M},

            new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},

            new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},

            new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}

        };

        // define the array to hold the results

        Product[] results = new Product[3];

        // sort the contents of the array

        Array.Sort(products, (item1, item2) => {

            return Comparer<decimal>.Default.Compare(item1.Price, item2.Price);

        });

        // get the first three items in the array as the results

        Array.Copy(products, results, 3);

        // print out the names

        foreach (Product p in results) {

            Console.WriteLine("Item: {0}, Cost: {1}", p.Name, p.Price);

        }

    }

}

With LINQ, we can significantly simplify the querying process, as demonstrated in Listing 5-27.
利用LINQ,我们可以明显地简化这一查询过程,如清单5-27所示。

Listing 5-27. Using LINQ to Query Data

using System;

using System.Linq;

class Program {

    static void Main(string[] args) {

 

        Product[] products = {

            new Product {Name = "Kayak", Category = "Watersports", Price = 275M},

            new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},

            new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},

            new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}

        };

        var results = from product in products

                         orderby product.Price descending

                         select new {

            product.Name,

            product.Price

        };

        int count = 0;

        // print out the names

        foreach (var p in results) {

            Console.WriteLine("Item: {0}, Cost: {1}", p.Name, p.Price);

            if (++count == 3) {

                break;

            }

        }

    }

}

This is a lot neater. You can see the SQL-like query shown in bold. We order the Product objects in descending order and use the select keyword to return an anonymous type that contains just the properties we want. This style of LINQ is known as query syntax, and it is the kind most developers are familiar with. The wrinkle in this query is that it returns one anonymously typed object for each Product in the array that we used in the source query, so we need to play around with the results to get the first three and print out the details.
这要简洁得多。你可以看到以黑体表示的类似于SQL的查询。我们以降序对Product对象进行排序,并用select关键词返回一个匿名类型,它只含有我们想要的属性。LINQ的这种风格称为查询语法,而且这是大多数开发人员熟悉的形式。这个查询的缺点是它为源查询数组中的每个Product返回一个匿名类型对象,因此,我们需要处理此结果,以获得最初的三个,并打印出细节。

However, if we are willing to forgo the simplicity of the query syntax, we can get a lot more power from LINQ. The alternative is the dot-notation syntax, or dot notation, which is based on extension methods. Listing 5-28 shows how we can use this alternative syntax to process our Product objects.
然而,如果我们愿意放弃这种查询语法的简单性,我们可以得到功能强大得多的LINQ。可用的办法是点符号语法,或点符号,这是基于扩展方法的。清单5-28演示了我们可以如何使用这种可选语法处理Product对象。

Listing 5-28. Using LINQ Dot Notation

using System;

using System.Linq;

class Program {

    static void Main(string[] args) {

        Product[] products = {

            new Product {Name = "Kayak", Category = "Watersports", Price = 275M},

            new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},

            new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},

            new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}

        };

 

        var results = products

                        .OrderByDescending(e => e.Price)

                        .Take(3)

                        .Select(e => new { e.Name, e.Price });

        foreach (var p in results) {

            Console.WriteLine("Item: {0}, Cost: {1}", p.Name, p.Price);

        }

    }

}

We’ll be the first to admit that this LINQ query, shown in bold, is not as nice to look at as the one expressed in query syntax, but not all LINQ features have corresponding C# keywords. For the serious LINQ programmer, we need to switch to using extension methods. Each of the LINQ extension methods in Listing 5-28 is applied to an IEnumerable<T> and returns an IEnumerable<T>, which allows us to chain the methods together to form complex queries.
我们将是第一次容许这种LINQ查询(以黑体表示)不像一个查询语法所表示的那样好看,但并不是所有LINQ特性都有相应的C#关键词。对一个严格的LINQ程序员来说,我们需要切换到使用扩展方法。清单5-28中的每一个LINQ扩展方法都运用于一个IEnumerable<T>并返回一个IEnumerable<T>,这允许我们把这些方法链接在一起形成复杂的查询。

n Note All of the LINQ extension methods are in the System.LINQ namespace, which you must bring into scope with a using statement before you can make queries.
注:所有LINQ扩展方法都在System.LINQ命名空间中,在能够进行查询之前,你必须用using语句引用它。

The OrderByDescending method rearranges the items in the data source. In this case, the lambda expression returns the value we want used for comparisons. The Take method returns a specified number of items from the front of the results (this is what we couldn’t do using query syntax). The Select method allows us to project our results, specifying the result we want. In this case, we are projecting an anonymous object that contains the Name and Price properties. Notice that we have not even needed to specify the names of the properties in the anonymous type. C# has inferred this from the properties we picked in the Select method.
OrderByDescending方法重组数据源中的条目。这里,lambda表达式返回我们要用来比较的值。Take方法返回结果最前面(这是我们用查询语法不能实现的)的一个指定数目的条目。Select方法允许我们设计结果,指定我们想要的结果。这里,我们设计一个匿名对象,它包含了NamePrice属性。注意,我们甚至不需要指定匿名类型中的属性名。C#已经根据我们在Select方法中拾取的属性推断了这些名字。

Table 5-1 describes the most useful LINQ extension methods. We use LINQ liberally throughout the result of this book, and you may find it useful to return to this table when you see an extension method that you haven’t encountered before. All of the LINQ methods shown in Table 5-1 operate on IEnumerable<T>.
5-1描述了最有用的LINQ扩展方法。本书中我们到处都使用LINQ,当你看到一个以前还未遇见的扩展方法时,回过来查看此表可能是很有用的。表5-1所显示的所有LINQ方法都是在IEnumerable<T>上进行操作。

Table 5-1. Some Useful LINQ Extension Methods

Extension Method
扩展方法

Description
描述

Deferred
是否延迟

First

Returns the first item from the data source
返回数据源的第一个条目

No

FirstOrDefault

Returns the first item from the data source or the default value if there are no items
返回数据源的第一个条目,或者,如果无条目时,返回默认值

No

Last

Returns the last item in the data source
返回数据源的最后一个条目

No

LastOrDefault

Returns the last item in the data source or the default value if there are no items
返回数据源的最后条目,或无条目时返回默认值

No

Max

Min

Returns the largest or smallest value specified by a lambda expression
返回由lambda表达式表示的最大值或最小值

No

OrderBy

OrderByDescending

Sorts the source data based on the value returned by the lambda expression
根据lambda表达式基于返回值进行排序

Yes

Reverse

Reverses the order of the items in the data source
保留数据源中的条目顺序

Yes

Select

Projects a result from a query
设计一个查询结果

Yes

SelectMany

Projects each data item into a sequence of items and then concatenates all of those resulting sequences into a single sequence
把每个数据条目投射到一个条目序列之中,然后把所有这些结果序列连接成一个序列

Yes

Single

Returns the first item from the data source or throws an exception if there are multiple matches
返回数据源的第一个适配卡,或者有多个匹配时弹出一个异常

No

SingleOrDefault

Returns the first item from the data source or the default value if there are no items, or throws an exception if there are multiple matches
返回数据源的第一个条目,或都没条目时返回默认值,或者有多个条目时返回一个异常

No

Skip

SkipWhile

Skips over a specified number of elements, or skips while the predicate matches
跳过指定数目的元素,或者当谓词匹配时跳过

Yes

Sum

Totals the values selected by the predicate
总和谓词选定的值

No

Take

TakeWhile

Selects a specified number of elements from the start of the data source or selects items while the predicate matches
从数据源的开始处选择指定数目的元素,或当谓词匹配时选择指定数目的条目

Yes

ToArray

ToDictionary

ToList

Converts the data source to an array or other collection type
把数据源转换成数组或其它集合类型

No

Where

Filters items from the data source that do not match the predicate
过滤掉源数据中与谓词不匹配的条目

Yes

Understanding Deferred LINQ Queries
延迟LINQ查询

You’ll notice that Table 5-1 includes a column called Deferred. There’s an interesting variation in the way that the extension methods are executed in a LINQ query. A query that contains only deferred methods isn’t executed until the items in the IEnumerable<T> result are enumerated, as demonstrated by Listing 5-29.
要注意到表5-1包含了一列Deferred。一条LINQ查询的扩展方法的执行方式有一个有趣的变异。含有deferred方法的一条查询直到IEnumerable<T>结果中的条目被枚举时才会执行,如清单5-29所示。

Listing 5-29. Using Deferred LINQ Extension Methods in a Query

using System;

using System.Linq;

class Program {

    static void Main(string[] args) {

        Product[] products = {

            new Product {Name = "Kayak", Category = "Watersports", Price = 275M},

            new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},

            new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},

            new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}

        };

        var results = products

                    .OrderByDescending(e => e.Price)

                    .Take(3)

                    .Select(e => new { e.Name, e.Price });

        products[2] = new Product { Name = "Stadium", Price = 79500M };

        foreach (var p in results) {

            Console.WriteLine("Item: {0}, Cost: {1}", p.Name, p.Price);

        }

    }

}

In this example, we create an array of Product objects, and then define the query we used in the previous section. After the query has been defined, we change one of the objects in the Product array and then enumerate the query results. The output from this example is as follows:
在这个例子中,我们生成了一个Product对象数组,然后定义我们在前一段的查询。在查询被定义之后,我们修改了Product数据中的一个对象,然后枚举查询结果,这个例子的输出如下:

Item: Stadium, Cost: 79500

Item: Kayak, Cost: 275

Item: Lifejacket, Cost: 48.95

You can see that the query isn’t evaluated until the results are enumerated, and so the change we made—introducing Stadium into the Product array—is reflected in the output. By contrast, using any of the nondeferred extension methods causes a LINQ query to be performed immediately. Listing 5-30 provides a demonstration.
你可以看到,直到结果被枚举时,才会评估这个查询,因此,我们所做的修改 Stadium引入到Product数组 会反映在输出中。相比之下,用任何无deferred扩展方法都会使LINQ查询立即执行。清单5-30提供了一个演示。

Listing 5-30. An Immediately Executed LINQ Query

using System;

using System.Linq;

class Program {

   static void Main(string[] args) {

        Product[] products = {

            new Product {Name = "Kayak", Category = "Watersports", Price = 275M},

            new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},

            new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},

            new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}

        };

        var results = products.Sum(e => e.Price);

        products[2] = new Product { Name = "Stadium", Price = 79500M };

        Console.WriteLine("Sum: {0:c}", results);

    }

}

This example uses the Sum method and produces the following results:
这个例子使用了Sum方法,并产生如下结果:

Sum: $378.40

You can see that the Stadium item, with its much higher price, has not been included in the results.
你可以看到,Stadium条目(它的价格要高得多)并没有被包含在结果中。

Repeatedly Using a Deferred Query
反复延迟查询

One interesting feature that arises from deferred LINQ extension methods is that queries are evaluated from scratch every time the results are enumerated, as shown in Listing 5-31.
延迟LINQ扩展方法所引起的一个有趣的特性是每次枚举结果时都是从头开始评估的,如清单5-31所示。

Listing 5-31. Repeatedly Executing a Deferred Query

using System;

using System.Linq;

class Program {

   static void Main(string[] args) {

        Product[] products = {

            new Product {Name = "Kayak", Category = "Watersports", Price = 275M},

            new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},

            new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},

            new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}

        };

        var results = products

                    .OrderByDescending(e => e.Price)

                    .Take(3)

                    .Select(e => new { e.Name, e.Price });

        foreach (var p in results) {

            Console.WriteLine("Item: {0}, Cost: {1}", p.Name, p.Price);

        }

        Console.WriteLine("---End of results---");

        products[2] = new Product { Name = "Stadium", Price = 79500M };

        foreach (var p in results) {

            Console.WriteLine("Item: {0}, Cost: {1}", p.Name, p.Price);

        }

    }

}

This example creates the data, defines a deferred LINQ query, and then enumerates the query results. One of the data elements is changed, and the results are enumerated once more. The results are as follows:
这个例子生成了数据,定义了一个延迟LINQ查询,然后枚举查询结果。修改了一个数据元素,再次枚举结果。结果如下:

Item: Kayak, Cost: 275

Item: Lifejacket, Cost: 48.95

Item: Corner flag, Cost: 34.95

---End of results---

Item: Stadium, Cost: 79500

Item: Kayak, Cost: 275

Item: Lifejacket, Cost: 48.95

---End of results---

You can see that the change to the data is reflected the second time the results are enumerated. We did not need to redefine, update, or in any way modify the LINQ query. This means that you can always rely on a deferred query to reflect the latest changes to the data source, but it also means that the results of the query are not cached. If you want to cache the results of a query, you should use a nondeferred method such as ToArray, which will force immediate query execution.
你可以看到,对数据的修改被反映到第二次结果枚举。我们不需要重新定义、更新、或修改LINQ查询的其它方法。意即,你可以总是依靠延迟查询来反映对数据源的最终修改,但它也意味着,查询结果是非缓冲的。如果你想缓冲一个查询结果,你应该用一个非延迟方法,如ToArray,它将强制立即执行查询。

LINQ AND THE IQUERYABLE<T> INTERFACE
LINQIQUERYABLE<T>接口

LINQ comes in different varieties, although using it is always pretty much the same. One variety is LINQ to Objects, which is what we’ve been using in the examples so far in this chapter. LINQ to Objects lets you query C# objects that are resident in memory. Another variety, LINQ to XML, is a very convenient and powerful way to create, process, and query XML content. Parallel LINQ is a superset of LINQ to Objects that supports executing LINQ queries concurrently over multiple processors or cores.
LINQ有不同的种类,虽然它的使用几乎总是相同的。一个种类是LINQ to Objects(对象查询),这是我们到目前为止还未在例子中使用的一种LINQ。它可以让你查询驻留在内存中的C#对象。另一个种类,LINQ to XMLXML查询),在生成、处理及查询XML内容方面十分方便且功能强大。并行LINQLINQ to Object的超集,它支持连续地在多处理器或多内核上执行查询。

Of particular interest to us is LINQ to Entities, which allows LINQ queries to be performed on data obtained from the Entity Framework. The Entity Framework is Microsoft’s ORM framework, which is part of the broader ADO.NET platform. An ORM allows you to work with relational data using C# objects, and it’s the mechanism we’ll use in this book to access data stored in databases. You’ll see how the Entity Framework and LINQ to Entities are used in the next chapter, but we wanted to mention the IQueryable<T> interface while we are introducing LINQ.
我们特别感兴趣的是LINQ to Entities(实体查询),它允许LINQEntity Framework(实体框架)获取的数据上执行查询。Entity Famework是微软件的ORM框架,这是ADO.NET平台的主要部分。ORM允许你用C#对象与关系数据一起工作,而且它是我们这本书所使用的访问存储在数据库中的数据的机制。你将在下一章看到Entity FrameworkLINQ to Entities的使用,但在介绍LINQ的时候,我们想提一下IQueryable<T>接口。

The IQueryable<T> interface is derived from IEnumerable<T> and is used to signify the result of a query executed against a specific data source. In our examples, this will be a SQL Server database. There is no need to use IQueryable<T> directly. One of the nice features of LINQ is that the same query can be performed on multiple types of data source (objects, XML, databases, and so on). When you see us use IQueryable<T> in examples in later chapters, it’s because we want to make it clear that we are dealing with data that has come from the database.
IQueryable<T>接口派生于IEnumerable<T>,并被用于表示对一个特定数据源执行的查询结果。在我们的例子中,这将是一个SQL Server数据库。不需要直接使用IQueryable<T>LINQ的一个很好的特性是同样的查询可以在多种数据源上执行(对象、XML、数据库等)。当你在后面的章节中看到我们使用IQueryable<T>时,这是因为我们想清楚地处理数据库的数据。

Understanding Razor Syntax
Razor语法

Razor is the name of the new view engine in MVC 3. The ASP.NET view engine processes web pages, looking for special elements that contain server-side instructions. As we’ve noted earlier, the standard ASPX view engine relies on the <% and %> elements, which are familiar to all ASP.NET developers.
RazorMVC 3的新视图引擎。ASP.NET视图引擎处理web页面,照管含有服务器端指令的特殊元素。正如我们前面已经提到的,标准的ASPX视图引擎依靠<%%>元素,这是所有ASP.NET开发人员都熟悉的。

With Razor, the MVC development team has introduced a new set of syntax elements, centered on the @ symbol. By and large, if you are familiar with the <% %> syntax, you won’t have too many problems with Razor, although there are a few new rules. In this section, we’ll give you a quick tour of the Razor syntax so you can recognize the new elements when you see them. We aren’t going to supply an exhaustive Razor reference; think of this more as a crash course in the syntax. We’ll explore Razor in depth as we continue through the book.
利用RazorMVC开发团队围绕@符号引入了一组新的语法元素。大体上,虽然有几个新规则,但如果你熟悉<% %>语法,运用Razor语法应该没有太多问题。本小节中,我们将给你提供一个快速的Razor语法教程,以使你看到它们时能够认识这个新元素。我们不打算提供详尽的Razor参考,而只把它考虑成一个语法速成。随着本书的继续我们将深度考查Razor

Creating the Project
生成项目

To demonstrate the features and syntax of Razor, let’s create an MVC project. Follow the instructions at the start of Chapter 3 to create an empty project using the MVC 3 template. We have called the project Razor.
为了演示Razor的特性和语法,让我们生成一个MVC项目。按照第3章开始的说明来生成一个MVC 3模板的空项目。我们已经调用了Razor

 Defining the Model
定义模型

We are going to use a very simple domain model that will contain a single domain class named Product. Add a file to your Models folder called Product.cs and ensure that the contents match those shown in Listing 5-32.
我们打算用一个十分简单的域模型,它只有一个域类,名为Product。添加一个文件到你的Models文件夹,名为Product.cs,并确保内容与清单5-32匹配。

Listing 5-32. Creating a Simple Domain Model Class

namespace Razor.Models {

    public class Product {

        public int ProductID { get; set; }

        public string Name { get; set; }

        public string Description { get; set; }

        public decimal Price { get; set; }

        public string Category { set; get; }

    }

}

This is the same Product class that we used to demonstrate the language features in the previous section.
这是在前面的章节中我们演示语言特性时所使用的同样的类。

Defining the Controller
定义控制器

Right-click the Controllers folder in your project, and select Add and then Controller from the pop-up menus. Set the name to ProductController and select Empty Controller for the Template option, as shown in Figure 5-1.
右击项目中的Controllers文件夹,选择“添加”,然后从弹出菜单选“控制器”。设置名字为ProductController,并从Template选项中选择empty controller,如图5-1所示。

   

Figure 5-1. Creating the ProductController
5-1. 生成ProductController

Click the Add button to create the Controller class, and then edit the contents of the file so that they match Listing 5-33.
点击Add按钮以生成这个控制器类,然后编辑文件内容使它如清单5-33

Listing 5-33. A Simple Controller

using System.Web.Mvc;

using Razor.Models;

namespace Razor.Controllers {

    public class ProductController : Controller {

        public ActionResult Index() {

            Product myProduct = new Product {

                ProductID = 1,

                Name = "Kayak",

                Description = "A boat for one person",

                Category = "Watersports",

                Price = 275M

            };

 

            return View(myProduct);

        }

    }

}

Our focus here is Razor, so we are going to play a little loose with the controller part of the MVC model. The Index action method creates an instance of Product and passes it to the View method without using a repository.
这里,我们的焦点是Razor,因此我们打算演示稍宽松的MVC模型的控制器。Index动作方法生成一个Product实例,并把它传递给未用存储库的View方法。

Creating the View
生成视图

To create the view, right-click the Index method of the ProductController class and select Add View. Check the option to create a strongly typed view and select the Product class from the drop-down list, as shown in Figure 5-2.
为了生成视图,右击ProductController类的Index方法,并选择添加视图。选中“生成强类型视图”复选框,并从下拉菜单列表中选择Product类,如图5-2所示。

 

Figure 5-2. Adding the Index view

n Note If you don’t see the Product class in the drop-down list, compile your project and try creating the view again. Visual Studio won’t recognize model classes until they are compiled.
注:如果你在下拉列表中没看到Product类,编译你的项目并再次生成视图。直到编译之后,Visual Studio才会识别出模型类。

Check the option to use a layout or master page, but don’t pick a file—just leave the text box empty, as shown in the figure. Click Add to create the view, which will appear in the Views/Product folder as Index.cshtml.
选中“使用布局或母板页”复选框,但不要拾取一个文件 只让这个文本框为空,如图所示。点击添加,以生成这个视图,它将以Index.cshtml出现在Views/Product文件夹中。

Setting the Default Route
设置默认路由

For convenience, we are going to tell MVC that requests to the / URL for our application should be directed to the Index action method of the Product controller. To do this, open the Global.asax file and find the RegisterRoutes method. In this method, there is a call to routes.MapRoute. Change the value assigned to the controller property from Default to Product, as shown in bold in Listing 5-34.
出于方便,我们打算告诉MVC对我们应用程序的/URL请求应该被引向Product控制器的Index动作方法。要完成这一步骤,打开Global.asax文件并找到RegisterRoutes方法。在这个方法中有一个对routes.MapRoute的调用。把赋给控制器属性的值从Default修改为Product,如清单5-34中的黑体所示。

Listing 5-34. Setting the Controller for the Default Route

routes.MapRoute(

    "Default", // Route name

    "{controller}/{action}/{id}", // URL with parameters

    new { controller = "Product", action = "Index", id = UrlParameter.Optional }

);

Change the value assigned to the controller property from Default to Product—you can see this change in bold in the listing. Don’t worry about the routes system just yet. we’ll explain how it works in Chapter 11.
把赋给控制器属性的值从Default修改为Product 你可以看到清单中以黑体表示的修改。对这个路由系统先不用着急。我们将在第11章解释它如何工作。

Examining a Basic Razor View
考查一个基本的Razor视图

Razor views have a file extension of .cshtml, as opposed to the .aspx extension used in previous MVC releases and in ASP.NET Web Forms. You can still use the ASPX view engine in an MVC 3 project, but we prefer the Razor engine, and it seems to be a strong area of focus for the Microsoft MVC development team.
Razor视图的文件扩展名为.cshtml,而以前的MVC版本和ASP.NET Web表单所使用的是.aspx。你在MVC 3项目中仍然可以用ASPX视图引擎,但我们更喜欢用Razor引擎,而它似乎是微软件MVC开发团队的一个很强的焦点方面。

If you open the Index.cshtml file for editing, you’ll see that the contents are similar to those shown in Listing 5-35. This is the starting point for a Razor view.
如果你打开Index.cshtml文件进行编辑,你将看到它的内容类似于清单5-35。这是Razor视图的起点。

Listing 5-35. A Simple Razor View

@model Razor.Models.Product

@{

    ViewBag.Title = "Index";

}

<h2>Index</h2>

Working with the Model Object
与模型对象进行工作

Despite its brevity, there’s a lot going on in Listing 5-35. Let’s start with the first line in the view:
不管它多短,清单5-35有很多事要做。让我们从视图的第一行开始:

@model Razor.Models.Product

Razor statements start with the @ character. In this case, we are defining the model view that we can refer to in the rest of the view, using @model. As we discussed in Chapter 3, a strongly typed view lets us pass a model object to the view. We can refer to methods, fields, and properties through the @Model property as demonstrated by Listing 5-36.
Razor语句以@字符开始。这里,我们用@model定义了在视图的其余部分可以使用的模型视图。正如我们在第3章中讨论的,强类型视图让我们把模型对象传递给视图。我们可以通过@Model属性引用方法、字段和属性,如清单5-36所示

Listing 5-36. Referring to the Model Object

@model Razor.Models.Product

@{

    ViewBag.Title = "Index";

}

<h2>Name: @Model.Name</h2>

You can see from the view listing that we usually don’t need to terminate a Razor statement unless we are using a code block, which we’ll describe shortly. Notice that when we specify the type of the model, we use @model (lowercase m), but when we refer to the model object, we use @Model (uppercase M). If you run the application (by selecting Start Debugging from the Visual Studio Debug menu), you see the result shown in Figure 5-3.
我们可以从这个视图的清单看出,我们通常不需要终止一条Razor语句,除非我们要用代码块(马上描述)。注意,当我们指定模型的类型时,我们用的是@model(小写m),而当我们引用模型对象时,我们用的是@Model(大写M)。如果你运行这个应用程序(从Visual Studio的“调试”菜单中选择“开始调试”),你可以看到运行结果,如图5-3所示。

 

Figure 5-3. Rendering the view in Listing 5-36
5-3. 清单5-36的渲染视图

Defining Razor Code
定义Razor代码

Listing 5-36 showed how to call code with Razor, like this:
清单5-36演示了如何调用Razor代码,像这样:

<h2>Name: @Model.Name</h2>

You aren’t limited to just calling the model, but it is the most common use of an inline tag. You can include any C# statement, as shown in Listing 5-37.
你不限于只调用模型,但这是最常用的行内标签。你可以包含任何C#语句,如清单5-37所示。

Listing 5-37. Calling Arbitrary Functions with Razor

@model Razor.Models.Product

@{

    ViewBag.Title = "Index";

}

<h2>Name: @Model.Name</h2>

Time view rendered: @DateTime.Now.ToShortTimeString()

This call returns the current time as a short string. Calling a function or property with Razor inserts the result into the HTML of the page. In the case of a call to @Model.<propertyname>, the value of the property is added to the page, and in the case of Listing 5-37, the current time is inserted.
这个调用以短字符串返回了当前时间。调用Razor的一个函数或属性,把结果插入到HTML页面。在调用@Model.<属性名>的情况下,该属性的值被添加到页面,在清单5-37中,插入了当前时间。

Razor can handle complex code blocks in much the same way. We start with the @ character, and then dive into the C# code, as shown in Listing 5-38.
Razor可以以十分相似的方式处理复杂的代码块。我们以@字符开始,然后进行C#编码,如清单5-38所示。

Listing 5-38. A More Complex Code Block

@model Razor.Models.Product

@{

    ViewBag.Title = "Index";

}

<h2>Name: @Model.Name</h2>

@if (Model.Category == "Watersports") {

    <p>@Model.Category <b>Splash!</b> </p>

}

Time view rendered: @DateTime.Now.ToShortTimeString()

In this example, we included an if statement that inserts additional content into the page when the Category property of the Product item is Watersports. Razor is smart enough to recognize that the statement in the if body starts with an HTML tag, and so treats it as markup to be emitted. It also looks for further @ tags, processes them, and puts the results into the web page, as shown in Figure 5-4.
在这个例子中,我们包含了一个if语句,当Product条目的Category属性是Watersports时,把附加内容插入到页面。Razor能够智能化地识别if体中的HTML标签,并把它处理为嵌入标记。它也进一步地照应着@标签,处理它们,并把结果放到web页面中,如图5-4所示。

 

Figure 5-4. The result of an if statement
5-4. if语句的结果

You need to give Razor a helping hand if you want to include text content inside a code block that doesn’t start with an HTML element. You do this with the @: tag., as shown in Listing 5-39.
如果你想在代码块中不以HTML元素标签开始来发布一行文本内容,你需要使用一个Razor的辅助工具。这就是使用@:标签,如清单5-39所示。

Listing 5-39. Content Inside a Code Block That Doesn’t Start with an HTML Element

@model Razor.Models.Product

@{

    ViewBag.Title = "Index";

}

<h2>Name: @Model.Name</h2>

@if (@Model.Category == "Watersports") {

    @:Category: @Model.Category <b>Splash!</b>

}

<p />

Time view rendered: @DateTime.Now.ToShortTimeString()

This is useful when you just want to insert some text into the page, usually as the content for an HTML element you have opened prior to the code block. When you prefix a line with @:, it tells Razor to treat the line as though it begins with an HTML element—for example, scan for other Razor tags, process them, and put the results into the rendered view. The result of this addition can be seen in Figure 5-5.
当你只想把一些文本插入到页面中时,这是很有用的,通常作为一个HTML元素的内容,你已经事先打开了一个代码。当你在一行的前面缀以@:时,它告诉Razor把它作为一行来处理,就好像是以一个HTML元素开始一样 例如,扫描其它Razor标签,处理它们,并把结果输出到渲染视图。这种附加的结果如图5-5所示。

 

Figure 5-5. Using the @: tag
5-5. 使用@:标签

If you need to include a number of lines, none of which start with HTML elements, you can use the text element, as shown in Listing 5-40. This is equivalent to prefixing each of the lines contained within the element with @:.
如果你需要包含数行文本,每一行都不用HTML元素开始,你可以用text元素,如清单5-40所示。这等同于每一行前面缀以@:元素(这似乎有点不相符,如果去掉以下代码中的<pre>标签,所有文本将仅显示为一行 译者)。

Listing 5-40. Using the Text Element

@model Razor.Models.Product

@{

    ViewBag.Title = "Index";

}

<h2>Name: @Model.Name</h2>

@if (@Model.Category == "Watersports") {

    <text>

        Category: @Model.Category <b>Splash!</b>

        <pre>

            Row, row, row your boat,

            Gently down the stream...

        </pre>

    </text>

}

<p />

Time view rendered: @DateTime.Now.ToShortTimeString()

You can still include Razor tags and HTML elements in the text block. This is just more convenient than using the @: tag when there are many lines to deal with. You can see the rendered view in Figure 5-6.
text块中你仍然可以包含Razor标记和HTML元素。这只是当有多行要处理时,比用@:更方便些。你可以在图5-6中看到渲染效果。

 

Figure 5-6. Using the text element
5-6. 使用text元素

Including Multiple Functions in a Code Block
在代码块中包含多个函数

You can group together larger regions of code and content by opening a code block with @{ and closing it with }. Listing 5-41 provides a demonstration.
你可以用@{并用}关闭的办法把大范围的代码和内容组合在一起。清单5-41提供了一个演示。

Listing 5-41. Creating a Larger Code Block

@model Razor.Models.Product

@{

    ViewBag.Title = "Index";

}

<h2>Name: @Model.Name</h2>

@{

    if (Model.Category == "Watersports") {

        @:Category: @Model.Category <b>Splash!</b>

    }

    if (Model.Price > 10) {

        <h5>Pricey!</h5>

    }

}

There are two if blocks here, which operate independently of each other. The result of referring to this view is shown in Figure 5-7.
这里有两个if块,相互进行独立的操作。视图的结果如图5-7所示。

 

Figure 5-7. Using large code blocks
5-7. 使用大代码块

A more common use for this kind of code block is to assign values to variables, as you can see at the top of the view page. There’s only one statement there at the moment (which we’ll explain shortly), but we could easily add more.
这种代码块更常见的使用是给变量赋值,正如你在视图页面顶部所看到的那种。这里只有一条语句(我们很快解释),但我们可以很容易地添加多条语句。

Passing Data Using the View Bag Feature
用视图包特性传递数据

In the previous chapter we saw how to use the View Data feature to pass data from the controller to the view. We can do the same thing using the View Bag feature. Listing 5-42 shows how to do this in the Product controller.
在上一章中,我们看到了如何使用View Data特性把数据从控制器传递给视图。我们可以用View Bag(视图包)特性做同样的事情。清单5-42演示了如何在Product控制器中做这件事。

Listing 5-42. Using the View Bag in the Controller

using System;

using System.Web.Mvc;

using Razor.Models;

namespace Razor.Controllers {

    public class ProductController : Controller {

        public ActionResult Index() {

            Product myProduct = new Product {

                ProductID = 1,

                Name = "Kayak",

                Description = "A boat for one person",

                Category = "Watersports",

                Price = 275M

            };

            ViewBag.ProcessingTime = DateTime.Now.ToShortTimeString();

            return View(myProduct);

        }

    }

}

ViewBag is a dynamic type, which means you can define properties by assigning values to them. There was no ProcessingTime until we assigned the current time to it. We use the same ViewBag format to read the data in the view, as demonstrated by Listing 5-43.
ViewBag是一个动态类型,意即,你可以通过对属性赋值来定义这些属性。ProcessingTime属性是不存在的,直到我们把当前时间赋给它之后才有它。我们在视图中用同样的ViewBag格式来读取数据,如清单5-43所示。

Listing 5-43. Using the View Bag in the View

@model Razor.Models.Product

@{

    ViewBag.Title = "Index";

}

<h2>Name: @Model.Name</h2>

@{

    if (Model.Category == "Watersports") {

        <text>

            <p>Description: @Model.Description <b>(Splash!)</b></p>

            <p>Category: @Model.Category</p>

        </text>

    } else {

        @:Description: @Model.Description

    }

}

View rendered at @ViewBag.ProcessingTime

You can see the rendered view in Figure 5-8.
渲染视图如5-8所示。

 

Figure 5-8. Reading data from the View Bag in a view

There isn’t any significant advantage in using ViewBag over ViewData—perhaps a few fewer key strokes, but nothing more. The reason we mention this feature is that it is used in the view that Visual Studio creates for us, in the first code block, as follows:
ViewData上使用ViewBag没有任何明显的优点 或许会少打几个键,再没有其它的了。我们提一下这个特性的原因是在Visual Studio为我们生成的视图中使用了它 第一个代码块,如下所示:

@{

    ViewBag.Title = "Index";

}

This block contains one statement, which assigns the string Index to a property called Title. In the next section, we’ll explain the significance of this.
这个块含有一条语句,把字符串赋给一个名为Title的属性。在下一小节,我们将解释其意义。

Working with Layouts
与布局一起工作

When we created the view, we specified that we wanted a layout, but we didn’t tell Visual Studio which one. If you look closely at the Add View dialog, you’ll see some useful information, as illustrated by Figure 5-9.
当我们生成视图时,我们指定了我们想用的布局,但我们并没有告诉Visual Studio用哪一个。如果你仔细考察添加视图对话框,你会看到一些有用的信息,如图5-9所示。

 

Figure 5-9. Specifying a Razor layout when creating a view
5-9. 当生成视图时指定一个Razor布局

The dialog tells us to leave the layout reference blank if it is already set in a _viewstart file. If you look in the Views folder in your MVC project, you will see a file called _ViewStart.cshtml, the contents of which are shown in Listing 5-46.
该对话框告诉我们,如果布局已经在_viewstart文件中设置了,那就让布局引用为空。如果你查看你MVC项目中的Views文件夹,你将看到一个名为_ViewStart.cshtml的文件,其内容如清单5-46所示。

Listing 5-46. The _ViewStart.cshtml File

@{

    Layout = "~/Views/Shared/_Layout.cshtml";

}

MVC looks for a _ViewStart.cshtml file when it renders a view and any instructions defined in the file are read and applied as though they were included in the view file itself. In this case, there is one instruction, which sets the layout for the view to be the _Layout.cshtml file in the Views/Shared folder.
MVC在渲染视图时会查找_ViewStart.cshtml文件,读取这个文件中的任何指令并运用它们,就好象它是包含在视图文件中一样。这里,只有一条指令,它为这个视图设置的布局是Views/Shared文件夹中的_Layout.cshtml文件。

n Note View files that start with an underscore (_) are not returned to the user, even if they are requested directly.
注:以下划线(_)开始的视图文件不会返回给用户,哪怕对它们直接请求。

This is how we are able to check the option to use a layout, but not tell Visual Studio which one we want, and let MVC figure it out automatically. Of course, we could have defined a layout explicitly in the view itself, but this approach means that we don’t need to duplicate the same setting in each view in our project. If we open the layout file, we can finally understand the significance of the ViewBag call in the Index view. The contents of _Layout.cshtml are shown in Listing 5-44.
这是我们如何才能够检查使用布局的选项,但并没有告诉Visual Studio我们想用哪一个,而让MVC自动推理。当然,我们可以在视图中明确地定义一个布局,但这种方法意味着我们项目中的每个视图不需要复制同样的设置。如果我们打开这个布局文件,我们可最终理解在Index视图中调用ViewBag的意义。_Layout.cshtml的内容如清单5-44所示。

Listing 5-44. The _Layout.cshtml File

<!DOCTYPE html>

<html>

<head>

    <title>@ViewBag.Title</title>

    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />

    <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")"

        type="text/javascript"></script>

</head>

<body>

    @RenderBody()

</body>

</html>

A layout is the equivalent of the ASPX master page. The ViewBag.Title property that we set in the Index view is read and used as the content of the HTML title element. Instead of an ASPX-style content placeholder, Razor includes the body of the view using the @RenderBody() call. Much like a master page, a Razor layout contains the HTML, script, and other elements that you want to avoid duplicating in your views. In this case, it included the basic structure of the HTML document, and the contents of the Index view will be rendered and inserted as the content for the HTML body element.
布局等价于ASPX的母板页。我们在Index视图设置的ViewBag.Title属性被读取,并用作为HTMLtitle元素的内容。相对于ASPX样式的内容部分,Razor使用了@RenderBody()调用的视图体。与母板页十分相似,Razor布局含有HTML、脚本、以及其它你希望在视图中避免复制的元素。这里,它包括了HTML文档的基本结构,以及Index视图将被渲染的内容和插入到HTMLbody元素中的内容。

Working Without Layouts
不使用页面布局

Razor layouts are optional. If you uncheck the layout option when you create a view, you’ll get a template like the one shown in Listing 5-45.
Razor布局是可选的。如果你在生成一个视图时未选布局选项,你将获得一个类似于清单5-45所示的模板。

Listing 5-45. A View That Doesn’t Use a Layout

@{

    Layout = null;

}

<!DOCTYPE html>

<html>

<head>

    <title>IndexNoTemplate</title>

</head>

<body>

    <div>

 

    </div>

</body>

</html>

Since there is no layout, the view must contain all of the content required to render a useful HTML page, including the html, head, and body elements. Notice that you must explicitly set Layout to null. If you don’t do this, the view will use the layout specified in the _ViewStart.cshtml file—something that catches us out surprisingly frequently.
由于没有布局,视图必须含有渲染一个HTML页面所需的全部内容,包括htmlhead、以及body元素。注意,你必须明确地把Layout设置为null。如果你不这么做,视图将用_ViewStart.cshtml文件中指定的布局 有时经常会让你感到莫明其妙。

Summary
概要

In this chapter, we started by giving an overview of the key C# language features that an effective MVC programmer needs to know about. These features are combined in LINQ, which we will use to query data throughout this book. As we said, we are big fans of LINQ, and it plays an important role in MVC applications.
本章中,我们从介绍一个高效的MVC程序员需要了解的关键的C#语言特性概述开始。这些特性被结合到LINQ之中,我们将用它来进行贯穿于本书的数据查询。正如我们曾说过的,我们是LINQ的大粉丝,而且它在MVC应用程序中起着重要的作用。

We also looked at the basic syntax of the Razor View Engine, which is new in MVC 3. The Razor syntax can be a little odd if you’re used to the ASPX <% and %> tags, but once you’ve mastered the basics, you’ll find that it’s quicker to use, easier to read, and more expressive overall. In the next chapter, we turn our attention to the key tools that make creating and testing MVC applications simpler and easier.
我们也考查了Razor视图引擎的基本语法,这是MVC 3的新内容。如果你习惯于使用ASPX<%%>标记,Razor语法可能有点特别,但一旦你掌握了这些基本知识,你将会发现它更好用、更易读、且更富有表现力。下一章中,我们把注意力转向使得生成和测试MVC应用程序更简单而容易的工具。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值