C#高级知识点概要(3) - 特性、自动属性、对象集合初始化器、扩展方法、Lambda表达式和Linq查询

1.特性(Attributes)

特性(Attributes),MSDN的定义是:公共语言运行时允许你添加类似关键字的描述声明,叫做attributes, 它对程序中的元素进行标注,如类型、字段、方法和属性等。Attributes和Microsoft .NET Framework文件的元数据保存在一起,可以用来向运行时描述你的代码,或者在程序运行的时候影响应用程序的行为。
例如,在一个方法前标注[Obsolete]特性,则调用该方法时VS则会提示该方法已过期的警告,如下图:

又如,在.Net Remoting的远程对象中,如果要调用或传递某个对象,例如类,或者结构,则该类或结构则必须标注[Serializable]特性。还有,我们在构建XML Web服务时用得很多的一个特性就是[WebMegthod],它可让通过HTTP请求的公开方法的返回值编码成XML进行传递。

特性实际上就是一个类,[Obsolete]特性的实际类名是ObsoleteAttribute,但我们在标注的时候可以不带Attribute后缀,系统在名称转换时会自动给我们加上。

上面说的都是些.NET系统定义的一些特性,当然还有很多。了解如何自定义特性,有利有我们更好的在ASP.NET MVC编程使用特性,比如给Model类的属性标注特性来验证表单输入的合法性(以后进行介绍)。

下面我们来模拟一个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; }
}

上面若不加AttributeUsage限制,特性可以声明在类型(如结构、类、枚举、委托)和成员(如方法,字段,事件,属性,索引)的前面。

然后我们把这个特性应用在下面的Order类之上:

// 应用自定义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; }

注意,自动属性不能定义只读或者只写的属性,必须同时提供get和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<Product>
products)可以直接调用该静态方法返回查询结果。解决问题的思想有了,接下来就是实现了。或许你对这一段描述有点蒙,结合代码可能让你更清晰。下
面是这个“商品查询器”-Filter方法的实现代码:

/// <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被调用之后才真正执行的。

时间: 2024-10-09 15:16:03

C#高级知识点概要(3) - 特性、自动属性、对象集合初始化器、扩展方法、Lambda表达式和Linq查询的相关文章

C#高级知识点概要(2) - 线程和并发

原文地址:http://www.cnblogs.com/Leo_wl/p/4192935.html 我也想过跳过C#高级知识点概要直接讲MVC,但经过前思后想,还是觉得有必要讲的.我希望通过自己的经验给大家一些指引,带着大家一起走上ASP.NET MVC大牛之路,少走弯路.同时也希望能和大家一起交流,这样也能发现我自己的不足,对我自己的帮助也是非常大的. 建议大家对C#撑握的不错的时候,可以去看一些开源项目.走技术这条路,就要耐得住寂寞(群里双休日说要让群主找妹子进群的人必须反思),练好内功.不

[ASP.NET MVC 大牛之路]03 - C#高级知识点概要(2) - 线程和并发

我也想过跳过C#高级知识点概要直接讲MVC,但经过前思后想,还是觉得有必要讲的.我希望通过自己的经验给大家一些指引,带着大家一起走上ASP.NET MVC大牛之路,少走弯路.同时也希望能和大家一起交流,这样也能发现我自己的不足,对我自己的帮助也是非常大的. 建议大家对C#撑握的不错的时候,可以去看一些开源项目.走技术这条路,就要耐得住寂寞(群里双休日说要让群主找妹子进群的人必须反思),练好内功.不撑握C#高级知识点,别想看懂优秀的开源项目,更别指望吸收其编程思想:你的水平,随时可以被一个实习生代

C#中的自动属性、隐式类型var、对象初始化器与集合初始化器、扩展方法

1.自动属性(Auto-Implemented Properties) //以前的写法 .net2.0 private string _userName; public string UserName { get { return _userName; } set { _userName= value; } } //现在 只适合3.5以上 public string_userName {get;set;} 2.隐式类型var 它是在编译已经能确定变量的类型,是根据后面的值自动推断类型,编译时把推

C#高级知识点概要(1) - 委托和事件

作者:linybo 要成为大牛,必然要有扎实的基本功,不然时间再长项目再多也很难有大的提升.本系列讲的C# 高级知识点,是非常值得去撑握的,不仅可以让你写代码时游刃有余,而且去研究和学习一些开源项目时,也不会显得那么吃力了. 希望大家记住,这里讲的所有的知识点,不仅仅是了解了就可以了,还要会灵活用,一定要多思考,撑握其中的编程思想. 本文讲的是委托和事件,这两个词可能你早就耳熟能详,但你是否真正撑握了呢? 本系列讲的C#高级知识点都是要求开发时能达到可以徒手写出来的水平(不依赖搜索引擎.找笔记等

[ASP.NET MVC 大牛之路]02 - C#高级知识点概要(1) - 委托和事件

在ASP.NET MVC 小牛之路系列中,前面用了一篇文章提了一下C#的一些知识点.照此,ASP.NET MVC 大牛之路系列也先给大家普及一下C#.NET中的高级知识点.每个知识点不太会过于详细,但足矣.要深入研究还需要去查看更多的专业资料. 要成为大牛,必然要有扎实的基本功,不然时间再长项目再多也很难有大的提升.本系列讲的C# 高级知识点,是非常值得去撑握的,不仅可以让你写代码时游刃有余,而且去研究和学习一些开源项目时,也不会显得那么吃力了. 希望大家记住,这里讲的所有的知识点,不仅仅是了解

《Entity Framework 6 Recipes》中文翻译系列 (26) ------ 第五章 加载实体和导航属性之延缓加载关联实体和在别的LINQ查询操作中使用Include()方法

翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-7  在别的LINQ查询操作中使用Include()方法 问题 你有一个LINQ查询,使用了类似这样的操作 group by,join,和where:你想使用Include()方法预先加载额外的实体.另外你想使用Code-First来管理数据访问. 解决方案 假设你有如图5-22所示的概念模型 图5-22 一个简单的包含Club和Event以及它们之间一对多关联的模型 在Visual S

C#高级知识点概要(2) - 线程并发锁

本文目录: 线程的简单使用 并发和异步的区别 并发控制 - 锁 线程的信号机制 线程池中的线程 案例:支持并发的异步日志组件 线程的简单使用 常见的并发和异步大多是基于线程来实现的,所以本文先讲线程的简单使用方法. 使用线程,我们需要引用System.Threading命名空间.创建一个线程最简单的方法就是在 new 一个 Thread,并传递一个ThreadStart委托(无参数)或ParameterizedThreadStart委托(带参数),如下: class Program { stat

阶段1 语言基础+高级_1-3-Java语言高级_04-集合_07 Collections工具类_1_Collections集合工具类的方法

这是一个个的添加的方式 参数是个可变的元素.可以传递任意多的元素 shuffle打乱集合元素顺序 原文地址:https://www.cnblogs.com/wangjunwei/p/11241224.html

[C#详解] (1) 自动属性、初始化器、扩展方法

代码下载:点我下载 目录 前言 属性与自动属性 属性 自动属性 初始化器 对象初始化器 集合初始化器 扩展方法 无参数扩展方法 带参数扩展方法 结尾 前言 首先祝大家2015新年快乐! 新的一年,新的开始.来博客园安家已经快两个月了.每天看博客.写博客.评论.回答博问已经渐渐养成了一种习惯.可以很明显的感觉到泡在博客园里真的可以学到很多,不论是技术文章的还是一些记叙经历.抒发感想的随笔,都让我从各个方面受益良多.不知道大家是否和我一样,就是感觉博客园有一种特殊的魔力,让你没事就想上去看一眼,看上