[ASP.NET
MVC Mavericks Road] 02 - C# のナレッジ ポイントの概要
このブログ投稿では、主に、asp.net mvc 開発で習得する必要がある C# 言語のナレッジ ポイントの簡単なレビューを提供します。特に、 C# 3.0 の C# 言語機能で利用できます。 asp.net mvc を学習している人は、数分かけて参照してみるとよいでしょう。この記事で確認する C# の知識ポイントは、プロパティ、自動プロパティ、オブジェクト コレクション初期化子、拡張メソッド、ラムダ式、Linq クエリです。 C# 上級の「プレイヤー」もお立ち寄りいただけます。
この記事の内容
1. 属性 (属性)
属性 (属性)、MSDN の定義: 共通言語ランタイムでは、プログラム内の要素に注釈を付ける、属性と呼ばれるキーワードに似た説明文を追加できます。タイプ、フィールド、メソッド、プロパティなど。属性は Microsoft .NET Framework ファイルのメタデータとともに保存され、ランタイムにコードを記述したり、プログラムの実行時にアプリケーションの動作に影響を与えたりするために使用できます。
たとえば、[Obsolete] 属性がメソッドの前にマークされている場合、VS は、以下に示すように、メソッドが呼び出されたときにメソッドが期限切れであるという警告を表示します。 .Net Remoting、クラスや構造体などのオブジェクトを呼び出したり渡したりする場合は、クラスまたは構造体を [Serializable] 属性でマークする必要があります。また、XML Web サービスを構築するときによく使用する機能は [WebMegthod] です。これを使用すると、HTTP リクエストのパブリック メソッドの戻り値を XML にエンコードして配信できます。
フィーチャーは実際にはクラスです。[Obsolete] フィーチャーの実際のクラス名は ObsoleteAttribute ですが、アノテーションを付けるときにシステムが自動的にそれを追加する必要はありません。
上記は .NET システムによって定義されている機能の一部です。もちろん、他にもたくさんの機能があります。機能をカスタマイズする方法を理解すると、フォーム入力の正当性を検証するための Model クラスの属性に注釈を付けるなど、ASP.NET MVC プログラミングの機能をより適切に使用するのに役立ちます (後述)。
ASP.NET MVC でよく使用される StringLenth 機能をシミュレートしてみましょう。この機能は、ユーザー入力が長さ制限を超えているかどうかを判断するために使用されます。さっそくシミュレーションしてみましょう。まず、MyStringLenth 属性を定義します:
// 用户自定义的带有可选命名参数的 MyStringLenthAttribute 特性类。 // 该特性通过AttributeUsage限制它只能用在属性和字段上。 [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public sealed class MyStringLenthAttribute : Attribute { public MyStringLenthAttribute(string displayName, int maxLength) { this.MaxLength = maxLength; this.DisplayName = displayName; } //显示的名称,对外是只读的,所以不能通过可选参数来赋值,必须在构造函数中对其初始化。 public string DisplayName { get; private set; } //长度最大值,对外是只读的,所以不能通过可选参数来赋值,必须在构造函数中对其初始化。 public int MaxLength { get; private set; } //错误信息,标注时可作为可选命名参数来使用。 public string ErrorMessage { get; set; } //长度最小值,标注时可作为可选命名参数来使用。 public int MinLength { get; set; } }
// 应用自定义MyStringLenth特性于Order类的OrderID属性之上。MinLength和ErrorMessage是命名参数。 public class Order { [MyStringLenth("订单号", 6,MinLength = 3, ErrorMessage = "{0}的长度必须在{1}和{2}之间,请重新输入!")] public string OrderID { get; set; } }最後に、MyStringLenth 機能を使用してユーザー入力文字列の長さを検証する方法を見ていきます:
//检查成员字符串长度是否越限。 private static bool IsMemberValid(int inputLength, MemberInfo member) { foreach (object attribute in member.GetCustomAttributes(true)) { if (attribute is MyStringLenthAttribute) { MyStringLenthAttribute attr=(MyStringLenthAttribute)attribute; string displayName = attr.DisplayName; int maxLength = attr.MaxLength; int minLength = attr.MinLength; string msg = attr.ErrorMessage; if (inputLength < minLength || inputLength > maxLength) { Console.WriteLine(msg, displayName, minLength, maxLength); return false; } else { return true; } } } return false; } //验证输入是否合法 private static bool IsValid(Order order) { if (order == null) return false; foreach (PropertyInfo p in typeof(Order).GetProperties()) { if (IsMemberValid(order.OrderID.Length, p)) return true; } return false; } public static void Main() { string input=string.Empty; Order order; do { Console.WriteLine("请输入订单号:"); input = Console.ReadLine(); order = new Order { OrderID = input }; } while (!IsValid(order)); Console.WriteLine("订单号输入正确,按任意键退出!"); Console.ReadKey(); }
2. 自動的なプロパティ
C# 3.0 以降では、プロパティのアクセサーに他のロジックが必要ない場合、自動的に実装されたプロパティにより、プロパティ宣言がより簡潔になります。
次の例は、属性の標準実装と自動実装を示しています:class Program { class Person { //标准实现的属性 int _age; public int Age { get { return _age; } set { if (value < 0 || value > 130) { Console.WriteLine("设置的年龄有误!"); return; } _age = value; } } //自动实现的属性 public string Name { get; set; } } static void Main(string[] args) { Person p = new Person(); p.Age = 180; p.Name = "小王"; Console.WriteLine("{0}今年{1}岁。",p.Name,p.Age); Console.ReadKey(); } }自動属性には、次のような異なるアクセス権を持たせることもできます:
public string Name { get;private set; }
public string Name { get; }//编译出错 public string PetName { set; }//编译出错
3. オブジェクトとコレクションの初期化子
上面我们演示自动属性的时候给对象的实例初始化时是一个一个属性进行赋值的,有多少个属性就需要多少句代码。C# 3.0和更高版本中有了对象集合初始化器,有了它,只需一句代码就可初始化一个对象或一个对象集合的所有属性。这在里先创建一个“商品”类,用于后面的示例演示:
/// <summary> /// 商品类 /// </summary> public class Product { /// <summary> /// 商品编号 /// </summary> public int ProductID { get; set; } /// <summary> /// 商品名称 /// </summary> public string Name { get; set; } /// <summary> /// 商品描述 /// </summary> public string Description { get; set; } /// <summary> /// 商品价格 /// </summary> public decimal Price { get; set; } /// <summary> /// 商品分类 /// </summary> public string Category { set; get; } }
基于上面定义好的商品类,下面代码演示了如何通过初始化器来创建商品类的实例对象和集合:
static void Main(string[] args) { //对象初始化器的使用 (可只给部分字段赋值) Product product = new Product { ProductID = 1234, Name = "西瓜", Price = 2.3M };//创建并初始化一个实例 //集合初始化器的使用 List<Product> proList = new List<Product> { new Product { ProductID = 1234, Name = "西瓜", Price = 2.3M }, new Product { ProductID = 2345, Name = "苹果", Price = 5.9M }, new Product { ProductID = 3456, Name = "樱桃", Price = 4.6M } }; //打印 Console.WriteLine("对象初始化器:{0} {1} {2}", product.ProductID, product.Name, product.Price); foreach (Product p in proList) { Console.WriteLine("集合初始化器:{0} {1} {2}", p.ProductID, p.Name, p.Price); } Console.ReadKey(); }
另外还有一些其它类型也可以使用初始化器,如下:
/数组使用初始化器 string[] fruitArray = {"apple","orange","plum" }; //匿名类型使用初始化器 var books = new { Title = "ASP.NET MVC 入门", Author = "小王", Price = 20 }; //字典类型使用初始化器 Dictionary<string, int> fruitDic = new Dictionary<string, int>() { { "apple", 10 }, { "orange", 20 }, { "plum", 30 } };
4.扩展方法
扩展方法使您能够向现有类型“添加”方法,而无需创建新的派生类型或修改原始类型。扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。例如,我们可以让Random类的所有实例对象拥有一个返回随机bool值的方法。我们不能对Random类本身进行修改,但可以对它进行扩展,如下代码所示:
static class Program { /// <summary> /// 随机返回 true 或 false /// </summary> /// <param name="random">this参数自动指定到Random的实例</param> /// <returns></returns> public static bool NextBool(this Random random) { return random.NextDouble() > 0.5; } static void Main(string[] args) { //调用扩展方法 Random rd = new Random(); bool bl = rd.NextBool(); Console.WriteLine(bl.ToString()); Console.ReadKey(); } }
注意,扩展方法必须在非泛型的静态类中定义,上面的Program类如不加static修饰符则会报错。
我们可以创建一个接口的扩展方法,这样实现该接口的类都可以调用该扩展方法。看下面一个完整示例:
/// <summary> /// 购物车类 (实现 IEnumerable<Product> 接口) /// </summary> public class ShoppingCart : IEnumerable<Product> { public List<Product> Products { get; set; } public IEnumerator<Product> GetEnumerator() { return Products.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } /// <summary> /// 定义一个静态类,用于实现扩展方法(注意:扩展方法必须定义在静态类中) /// </summary> public static class MyExtensionMethods { /// <summary> /// 计算商品总价钱 /// </summary> public static decimal TotalPrices(this IEnumerable<Product> productEnum) { decimal total = 0; foreach (Product prod in productEnum) { total += prod.Price; } return total; } } class Program { static void Main(string[] args) { // 创建并初始化ShoppingCart实例,注入IEnumerable<Product> IEnumerable<Product> products = new ShoppingCart { Products = new List<Product> { new Product {Name = "Kayak", Price = 275}, new Product {Name = "Lifejacket", Price = 48.95M}, new Product {Name = "Soccer ball", Price = 19.50M}, new Product {Name = "Corner flag", Price = 34.95M} } }; // 创建并初始化一个普通的Product数组 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} }; // 取得商品总价钱:用接口的方式调用TotalPrices扩展方法。 decimal cartTotal = products.TotalPrices(); // 取得商品总价钱:用普通数组的方式调用TotalPrices扩展方法。 decimal arrayTotal = productArray.TotalPrices(); Console.WriteLine("Cart Total: {0:c}", cartTotal); Console.WriteLine("Array Total: {0:c}", arrayTotal); Console.ReadKey(); } }
执行后输出如下结果:
5.Lambda 表达式
Lambda 表达式和匿名函数其实是一件事情。不同是,他们语法表现形式不同,Lambda 表达式在语法上实际上就是匿名函数的简写。直接介绍匿名函数和Lambda表达式的用法没什么意思,在这里,我要根据实际应用来讲一个两者用法的例子,这样在介绍知识点的同时也能和大家分享一下解决问题的思想。
假如我们要实现一个功能强大的商品查询方法,这个商品查询方法如何查询商品是可以由用户自己来决定的,用户可以根据价格来查询商品,也可以根据分类来查询商品等等,也就是说用户可以把自己的查询逻辑传递给这个查询方法。要编写这样一个方法,我们很自然的会想到用一个委托来作为这个方法的参数,这个委托就是用户处理商品查询的逻辑。 我们不防把这个查询方法称为“商品查询器”。我们可以用静态的扩展方法来实现这个“商品查询器“,这样每个商品集合对象(如 IEnumerable
/// <summary> /// 定义一个静态类,用于实现扩展方法 /// </summary> public static class MyExtensionMethods { /// <summary> /// 商品查询器 /// </summary> /// <param name="productEnum">扩展类型的实例引用</param> /// <param name="selectorParam">一个参数类型为Product,返回值为bool的委托</param> /// <returns>查询结果</returns> public static IEnumerable<Product> Filter(this IEnumerable<Product> productEnum, Func<Product, bool> selectorParam) { foreach (Product prod in productEnum) { if (selectorParam(prod)) { yield return prod; } } } }
没错,我们就是用这么简短的Filter方法来满足各种需求的查询。上面Product类使用的是前文定义的。这里也再一次见证了扩展方法的功效。为了演示Filter查询方法的调用,我们先来造一批数据:
static void Main(string[] args) { // 创建商品集合 IEnumerable<Product> products = new ShoppingCart { Products = new List<Product> { new Product {Name = "西瓜", Category = "水果", Price = 2.3M}, new Product {Name = "苹果", Category = "水果", Price = 4.9M}, new Product {Name = "ASP.NET MCV 入门", Category = "书籍", Price = 19.5M}, new Product {Name = "ASP.NET MCV 提高", Category = "书籍", Price = 34.9M} } }; }
接下来我们继续在上面Main方法中来调用查询方法Filter:
//用匿名函数定义一个具体的查询需求 Func<Product, bool> fruitFilter = delegate(Product prod) { return prod.Category == "水果"; }; //调用Filter,查询分类为“水果”的商品 IEnumerable<Product> filteredProducts = products.Filter(fruitFilter); //打印结果 foreach (Product prod in filteredProducts) { Console.WriteLine("商品名称: {0}, 单价: {1:c}", prod.Name, prod.Price); } Console.ReadKey();
输出结果为:
上面我们使用的是委托和匿名函数来处理用户查询逻辑,并把它传递给Filter方法,满足了前面所说的需求。但若使用Lambda表达式代替上面的匿名函数能使上面的代码看上去更简洁更人性化,如下代码所示:
Func<Product, bool> fruitFilter = prod => prod.Category == "水果"; IEnumerable<Product> filteredProducts = products.Filter(fruitFilter);
没有了delegate关键字,没有了大小括号,看上去更舒服。当然上面两行代码可以继续简化为一行:
IEnumerable<Product> filteredProducts = products.Filter(prod => prod.Category == "水果");
这三种方式输出结果都是一样的。然后,我们还可以通过Lambda表达式实现各种需求的查询:
//查询分类为“水果”或者单价大于30元的商品 IEnumerable<Product> filteredProducts = products.Filter(prod => prod.Category == "水果" || prod.Price > 30 );
通过这个示例,相信大家已经清晰的了解并撑握了Lambda表达式的简单应用,而这就足够了:)。
6.LINQ
最后简单回顾一下LINQ。LINQ(Language Integrated Query语言集成查询)是 VS 2008 和 .NET Framework 3.5 版中一项突破性的创新,它在对象领域和数据领域之间架起了一座桥梁。
上面讲Lambda表达式时,用到的查询结果集的方式未免还是有点麻烦(因为自定义了一个Filter扩展方法),而Linq本身就集合了很多扩展方法,我们可以直接使用,大大的简化了编写查询代码的工作。例如,对于这样一个数据集合:
Product[] products = { new Product {Name = "西瓜", Category = "水果", Price = 2.3M}, new Product {Name = "苹果", Category = "水果", Price = 4.9M}, new Product {Name = "空心菜", Category = "蔬菜", Price = 2.2M}, new Product {Name = "地瓜", Category = "蔬菜", Price = 1.9M} };
如果要查询得到价钱最高的三个商品信息,如果不使用Linq,我们可能会先写一个排序方法,对products根据价钱由高到低进行排序,排序时需要创建一个新的Product[]对象用于存储排序好的数据。但用Linq可大大减少工作量,一两句代码就能搞定。如下代码所示,查出价钱最高的三个商品:
var results = from product in products orderby product.Price descending select new { product.Name, product.Price }; //打印价钱最高的三个商品 int count = 0; foreach (var p in results) { Console.WriteLine("商品:{0},价钱:{1}", p.Name, p.Price); if (++count == 3) break; } Console.ReadKey();
输出结果:
能熟练使用Linq是一件很爽的事情。上面的Linq语句和我们熟悉的SQL查询语句类似,看上去非常整洁且易懂。但并不是每一种SQL查询语句在C#都有对应的关键字,有时候我们需要使用另外一种Linq查询方式,即“点号”方式的Linq查询方式,这种方式中的Linq查询方法都是扩展方法。如下面这段代码和上面实现的效果是一样的:
var results = products .OrderByDescending(e => e.Price) .Take(3) .Select(e => new { e.Name,e.Price}); foreach (var p in results) { Console.WriteLine("商品:{0},价钱:{1}", p.Name, p.Price); } Console.ReadKey();
虽然类SQL的Linq查询方式比这种方式看上去更一目了然,但并不是每一种SQL查询语句在C#都有对应的关键字,比如这里的Take扩展方法就是类SQL的Linq查询语法没有的功能。
注意,有些Linq扩展方法分为“延后查询”(deferred)和“即时查询”(immediate)。延后查询意思是拥有“延后查询”扩展方法的Linq语句只有当调用结果集对象的时候才开始真正执行查询,即时查询则是立即得到结果。比如上面的Linq语句的OrderByDescending扩展方法就是一个“延后查询”方法,当程序执行到Linq语句定义部分时并没有查询出结果并放到results对象中,而是当程序执行到foreach循环时才真正执行Linq查询语句得到查询结果。我们可以做个测试,在Ling语句之后,我们再将products[1]对象重新赋值,如下代码所示:
var results = products .OrderByDescending(e => e.Price) .Take(3) .Select(e => new { e.Name, e.Price }); //在Linq语句之后对products[1]重新赋值 products[1] = new Product { Name = "榴莲", Category = "水果", Price = 22.6M }; //打印 foreach (var p in results) { Console.WriteLine("商品:{0},价钱:{1}", p.Name, p.Price); } Console.ReadKey();
输出结果为:
我们发现results是重新赋值之后的结果。可想而知,查询语句是在results被调用之后才真正执行的。
Linq非常强大也非常好用,这里只是把它当作一个学习ASP.NET MVC之前需掌握的知识点进行简单回顾。要灵活熟练地使用Linq还需要经常使用才行。
以上就是[ASP.NET MVC 小牛之路]02 - C#知识点提要的内容,更多相关内容请关注PHP中文网(www.php.cn)!