• C# & .NET 1.04.0


    学习和使用.NET平台已有六七个年头了,05年开始学习.NET的时候,当时网上的资源不像现在这样丰富,所以去电脑城买了张盗版的VS2005的光盘,安装时才发现是VS2003,当时有一种被坑的感觉,但也正是如此,让我有了一个完整的.NET的学习生涯。 从我从业的经历来看VS2003和VS2008是使用的比较多的,一些历史遗留的老项目由种种原因没有升级,依然在使用VS2003。我现在所在的公司也只使用VS2003和VS2008这两种开发工具,尽管TFS已经是2010了。很多时候公司觉得够用也就不去升级开发工具了。从1.0到4.0的变化主要体现在DotNet平台、语言和开发工具这三个方面。本文只是概括性的讲述在每个版本里都有些什么新的特性。先看下面的一张表格,描述的不同版本的出生年份、对应的VS版本和CLR的版本。

    上图中需要注意的一点是:2.0 3.0 3.5的CLR版本都是2.0。


    在1.1版本中,从语言的角度就是基本的面向对象的语法,可以说任何一般C#语言的书籍都包含了C#1的所有内容。工具方面VS2003已经很强大了,即便是到了现在的VS2012,我们80%时候用到的也只不过是20%的功能。 在VS2003中创建ASP.NET项目默认是跟IIS相关联的,从VSS中拉VS2003的代码时需要先在IIS中创建好站点,端口要和VSS中代码的相对应,但有时会出现很多问题。我一般的做法是将代码拉到本地后,修改解决方案文件中的路径为本地路径,然后修改项目文件中的ProjectType为Local,默认是Web,就可以很顺利打开项目了。


    2.0中对应VS2005我用的也不多,因为很快就被VS2008替代了,不过在语言方面却带来了很多新的东西。

    ★C#2中最重要的一个特性应该就是泛型。泛型的用处就是在一些场景下可以减少强制转换来提高性能。在C#1中就有很多的强制转换,特别是对一些集合进行遍历时,如ArrayList、HashTable,因为他们是为不同数据类型设计的集合,所以他们中键和值的类型都是object,这就意味着会平凡发生装箱拆箱的才做。C#2中有了泛型,所以我们可以使用List<T>、Dictionary<Tkey,TValue> 。泛型能够带来很好的编译时类型检查,也不会有装箱拆箱的才做,因为类型是在使用泛型的时候就已经指定了。 DotNet已经通过了很多的泛型类型供我们使用,如上面提到的List<T>,Dictionary<Tkey,TValue>,我们也可以自己来创建泛型类型(类、接口、委托、结构)或是方法。在定义泛型类型或时可以通过定义泛型约束来对泛型参数进行限制,更好的使用编译时检查。泛型约束是通过关键字where来实现的,C#2中的泛型约束有4种:

    • 引用类型约束:确保类型实参是引用类型,使用where T:class来表示;
    • 值类型约束:确保类型实参是值类型,使用where T:truct来表示;
    • 构造函数类型约束,使用where T:new()来表示;
    • 转换类型约束:约束类型实参是另外的一种类型,例如:where T:IDisposable 。

    ★C#2提供了分部类(Partil)。分部类可以允许我们在多个文件中为一个类型编写代码,在Asp.Net2.0中用的极为广泛。新建一个Aspx页面,页面的CodeBehind和页面中的控件的定义就是通过分部类来实现的。如下:

    public partial class _Default : System.Web.UI.Page 
    public partial class _Default 

    分布类使用关键字partial来定义,当一个类中的代码非常多时,可以使用分部类来进行拆分,这对代码的阅读很有好处,而且不会影响调用。

    ★C#2中增加了静态类,静态类中的公用方法必须也是静态的,可以由类名直接调用,不需要实例化,比较适用于编写一些工具类。如System.Math类就是静态类。工具类有一些特点,如:所有成员都是静态的、不需要被继承、不需要进行实例化。在C#1中我们可以通过如下代码来实现:

    //声明为密封类防止被继承 
    public sealed class StringHelper
    {
        //添加私有无参构造函ˉ数防止被实例化,如果不添加私有构造函数 //会自动生成共有无参构造函数 
        private StringHelper(){};
        public static int StringToInt32(string input)
        {
            int result=0;
            Int32.TryParse(input, out result);
            return result;
        }
    }

    C#2中可以使用静态类来实现:

    public static class StringHelper
    {
        public static int StringToInt32(string input)
        {
            int result=0;
            Int32.TryParse(input, out result);
            return result;
        }
    }

    ★在C#1中声明属性,属性中的get和set的访问级别是和属性一致,要么都是public要么都是private,如果要实现get和set有不同的访问级别,则需要用一种变通的方式,自己写GetXXX和SetXXX方法。在C#2中可以单独设置get和set的访问级别,如下:

    private string name;
    public string Name
    {
        get { return name; }
        private set { name = value; }
    }

    需要注意的是,不能讲属性设置为私有的,而将其中的get或是set设置成公有的,也不能给set和get设置相同的访问级别,当set和get的访问级别相同时,我们可以直接设置在属性上。

    ★命名空间可以用来组织类,当不同的命名空间中有相同的类时,可以使用完全限定名来防止类名的冲突,C#1中可以使用空间别名来简化书写,空间别名用using关键字实现,看下面一个例子:

    namespace NameSpaceDemo1
    {
        public class ClassDemo{}
    }
    namespace NameSpaceDemo2
    {
        public class ClassDemo{}
    }
    using Demo1 = NameSpaceDemo1;
    using Demo2 = NameSpaceDemo2;
    namespace WebApplication2
    {
        public partial class _Default : System.Web.UI.Page 
        {
            protected void Page_Load(object sender, EventArgs e)
            {
                Demo1.ClassDemo c1 = new Demo1.ClassDemo();
                Demo2.ClassDemo c2 = new Demo2.ClassDemo();
            }
        }
    }

    如果我们在使用中添加了名为Demo1或是Demo2的类,那么空间别名的使用就会受到影响,在C#1中就得使用完全限定名了。在C#2中可以用“::”符号来解决,如下面代码:

    namespace WebApplication2
    {
        class Demo1 { }
        public partial class _Default : System.Web.UI.Page 
        {
            protected void Page_Load(object sender, EventArgs e)
            {
                Demo1::ClassDemo c1 = new Demo1::ClassDemo();
                Demo2.ClassDemo c2 = new Demo2.ClassDemo();
            }
        }
    }

    如果有两个类,一个在包含在命名空间中,一个没有包含在命名空间中,那么要访问没有包含在命名空间中的类时,在C#2中可以使用“global::”,代码如下:

    public class ClassDemo{}
    namespace NameSpaceDemo1
    {
        public class ClassDemo{}
        public class Test 
        {
            static void Main()
            {
                Console.WriteLine(typeof(ClassDemo)); //NameSpaceDemo1.ClassDemo 
                Console.WriteLine(typeof(global::ClassDemo)); //ClassDemo 
                Console.ReadLine();
            }
        }
    }

    还有一种命名空间别名叫外部别名,用来处理不同的dll中存在相同的命名空间和类型即完全限定名相同,假设Firs.dll和Second.dll中都有完全限定名为“Demo.Class1"的类型,这时使用完全限定名也无法区分,就必须使用外部别名了,代码如下:

    extern alias FirstDemo;
    extern alias SecondDemo;
    using System;
    namespace NameSpaceDemo1
    {
        public class ClassDemo{}
        public class Test 
        {
            static void Main()
            {
                Console.WriteLine(typeof(FirstDemo::Demo.Class1));
                Console.WriteLine(typeof(SecondDemo::Demo.Class1));
                Console.ReadLine();
            }
        }
    }

    使用外部别名有两点需要注意:

    1 extern alias必须声明在最上方;

    2 引用了第三方dll,如First.dll和Second.dll,需要修改引入dll的Aliasces属性值,像上面的例子中分别是FirstDemo和SecondDemo,在引入的dll上点击右键,选择属性,如下:

    ★C#2中还提供了友元程序集,具体参考我以前博文《C#:友元程序集

    ★C#2中提供了可空类型,说直白点就是允许值类型的值为null。正常来说值类型的值是不应该为null的,但我们的很多应用是和数据库打交道的,而数据库中的类型都是可以为null值的,这就造成了我们写程序的时候有时需要将值类型设置为null。在C#1中通常使用”魔值“来处理这种情况,比如DateTiem.MinValue、Int32.MinValue。在ADO.NET中所有类型的空值可以用DBNull.Value来表示。C#2中可空类型主要是使用System.Nullable<T>的泛型类型,类型参数T有值类型约束。可以像下面这样来定义可空类型:

    Nullable<int> i = 20;
    Nullable<bool> b = true;

    C#2中也提供了更方便的定义方式,使用操作符?:

    int? i = 20;
    bool? b = true;

    ★C#2中对迭代器提供了更便捷的实现方式。提到迭代器,有两个概念需要了解,可枚举对象和枚举器,实现了System.Collections.IEnumerable接口的对象是可枚举对象,这些对象可以被C#中的foreach进行迭代;实现了System.Collections.IEnumeror接口的对象被称为枚举器。在C#1中实现迭代器非常繁琐,看下面一个例子:

    public class Test 
    {
        static void Main()
        {
            Person arrPerson = new Person("oec2003","oec2004","oec2005");
            foreach (string p in arrPerson)
            {
                Console.WriteLine(p);
            }
            Console.ReadLine();
        }
    }
    public class Person:IEnumerable 
    {
        public Person(params string[] names)
        {
            _names = new string[names.Length];
            names.CopyTo(_names, 0);
        }
        public string[] _names;
        public IEnumerator GetEnumerator()
        {
            return new PersonEnumerator(this);
        }
        private string this[int index]
        {
            get { return _names[index]; }
            set { _names[index] = value; }
        }
    }
    public class PersonEnumerator : IEnumerator 
    {
        private int _index = -1;
        private Person _p;
        public PersonEnumerator(Person p) { _p = p; }
        public object Current
        {
            get { return _p._names[_index]; }
        }
        public bool MoveNext()
        {
            _index++;
            return _index < _p._names.Length;
        }
        public void Reset()
        {
            _index = -1;
        }
    }

    C#2中的迭代器变得非常便捷,使用关键字yield return关键字实现,下面是C#2中使用yield return的重写版本:

    public class Test 
    {
        static void Main()
        {
            Person arrPerson = new Person("oec2003","oec2004","oec2005");
            foreach (string p in arrPerson)
            {
                Console.WriteLine(p);
            }
            Console.ReadLine();
        }
    }
    public class Person:IEnumerable 
    {
        public Person(params string[] names)
        {
            _names = new string[names.Length];
            names.CopyTo(_names, 0);
        }
        public string[] _names;
        public IEnumerator GetEnumerator()
        {
            foreach (string s in _names)
            {
                yield return s;
            }
        }
    }

    ★C#2中提供了匿名方法,匿名方法比较适用于定义必须通过委托调用的方法,用多线程来举个例子,在C#1中代码如下:

    private void btnTest_Click(object sender, EventArgs e)
    {
        Thread thread = new Thread(new ThreadStart(DoWork));
        thread.Start();
    }
    private void DoWork()
    {
        for (int i = 0; i < 100; i++)
        {
            Thread.Sleep(100);
            this.Invoke(new Action<string>(this.ChangeLabel),i.ToString());
        }
    }
    private void ChangeLabel(string i)
    {
        label1.Text = i + "/100";
    }

    使用C#2中的匿名方法,上面的例子中可以省去DoWork和ChangeLabel两个方法,代码如下:

    private void btnTest_Click(object sender, EventArgs e)
    {
        Thread thread = new Thread(new ThreadStart(delegate() {
            for (int i = 0; i < 100; i++)
            {
                Thread.Sleep(100);
                this.Invoke(new Action(delegate() { label1.Text = i + "/100"; }));
            }
        }));
        thread.Start();
    }

    ★C#2对数据类型的转换提供了TryParse,可以更好的进行容错,看下面代码:

    static void Main()
    {
        int a = 0;
        string b = "30", c = "aaa";
        int.TryParse(b, out a);
        Console.WriteLine(a);//30 int.TryParse(c, out a);
        Console.WriteLine(a);//0 Console.ReadLine();
    }

    如果说C#2中的核心是泛型的话,那么C#3中的核心就应是Linq了,C#3中的特性几乎都是为Linq服务的,但每一项特性都可以脱离Linq来使用。下面就来看下C#3中有哪些特性。 ★自动实现的属性,这个特性非常简单,就是使定义属性变得更简单了。代码如下:

    public string Name { get; set; }
    public int Age { private set; get; }

    在VS中输入prop,并且按tab键两次,就可以快速的添加属性。

    ★隐式类型的局部变量,该特性是让我们在定义变量时可以比较动态化,使用var关键字作为类型的占位符,然后由编译器来推导变量的类型。具体参见《C#3.0学习(1)---隐含类型局部变量和扩展方法》。隐式类型虽然让编码方便了,但有些不少限制:

    • 被声明的变量只能是局部变量,而不能是静态变量和实例字段;
    • 变量在声明的同时必须初始化,初始化值不能为null;
    • 语句中只能声明一个变量;

    ★对象集合初始化器,简化了对象和集合的创建,具体参见《C#3.0学习(2)---对象集合初始化器》。

    ★隐式类型的数组,和隐式类型的局部变量类似,可以不用显示指定类型来进行数组的定义,通常我们定义数组是这样:

    string[] names = { "oec2003", "oec2004", "oec2005" };

    使用匿名类型数组可以想下面这样定义:

    protected void Page_Load(object sender, EventArgs e)
    {
        GetName(new[] { "oec2003", "oec2004", "oec2005" });
    }
    public string GetName(string[] names)
    {
        return names[0];
    }

    ★匿名类型 ,具体参见《C#3.0学习(3)—匿名类型》。

    ★扩展方法,可以在现有的类型上添加一些自定义的方法,比如可以在string类型上添加一个扩展方法ToInt32,就可以像“20”.ToInt32()这样调用了。具体参见《C#3.0学习(1)---隐含类型局部变量和扩展方法》。

    ★Lambda表达式,实际上是一个匿名方法,Lambda表达的表现形式是:(参数列表)=>{语句},看一个例子,创建一个委托实例,获取一个string类型的字符串,并返回字符串的长度。代码如下:

    Func<string, int> func = delegate(string s) { return s.Length; };
    Console.WriteLine(func("oec2003"));

    使用Lambda的写法如下:

    Func<string, int> func = (string s)=> { return s.Length; };
    Func<string, int> func1 = (s) => { return s.Length; };
    Func<string, int> func2 = s => s.Length;

    上面三种写法是逐步简化的过程。

    ★Lambda表达式树,是.NET3.5中提出的一种表达方式,提供一种抽象的方式将一些代码表示成一个对象树。要使用Lambda表达式树需要引用命名空间System.Linq.Expressions,下面代码构建一个1+2的表达式树,最终表达式树编译成委托来得到执行结果:

    Expression a = Expression.Constant(1);
    Expression b = Expression.Constant(2);
    Expression add = Expression.Add(a, b);
    Console.WriteLine(add); //(1+2) Func<int> fAdd = Expression.Lambda<Func<int>>(add).Compile();
    Console.WriteLine(fAdd()); //3 

    Lambda和Lambda表达式树为我们使用Linq提供了很多支持,如果我们在做的一个管理系统使用了Linq To Sql,在列表页会有按多个条件来进行数据的筛选的功能,这时就可以使用Lambda表达式树来进行封装查询条件,下面的类封装了And和Or两种条件:

    public static class DynamicLinqExpressions 
    {
        public static Expression<Func<T, bool>> True<T>() { return f => true; }
        public static Expression<Func<T, bool>> False<T>() { return f => false; }
    
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
                                                            Expression<Func<T, bool>> expr2)
        {
            var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
            return Expression.Lambda<Func<T, bool>>
                  (Expression.Or(expr1.Body, invokedExpr), expr1.Parameters);
        }
    
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1,
                                                             Expression<Func<T, bool>> expr2)
        {
            var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
            return Expression.Lambda<Func<T, bool>>
                  (Expression.And(expr1.Body, invokedExpr), expr1.Parameters);
        }
    }

    下面是获取条件的方法:

    public Expression<Func<Courses, bool>> GetCondition()
    {
        var exp = DynamicLinqExpressions.True<Courses>();
        if (txtCourseName.Text.Trim().Length > 0)
        {
            exp = exp.And(g => g.CourseName.Contains(txtCourseName.Text.Trim()));
        }
        if (ddlGrade.SelectedValue != "-1")
        {
            exp=exp.And(g => g.GradeID.Equals(ddlGrade.SelectedValue));
        }
        return exp;
    }

    ★Linq是一个很大的话题,也是NET3.5中比较核心的内容,有很多书籍专门来介绍Linq,下面只是做一些简单的介绍,需要注意的是Linq并非是Linq To Sql,Linq是一个大的集合,里面包含:

    • Linq To Object:提供对集合和对象的处理;
    • Linq To XML:应用于XML;
    • Linq To Sql:应用于SqlServer数据库;
    • Linq To DataSet: DataSet;
    • Linq To Entities:应用于SqlServer之外的关系数据库,我们还可以通过Linq的扩展框架来实现更多支持Linq的数据源。

    下面以Linq To Object为例子来看看Linq是怎么使用的:

    public class UserInfo 
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    public class Test 
    {
        static void Main()
        {
            List<UserInfo> users = new List<UserInfo>()
            {
                new UserInfo{Name="oec2003",Age=20},
                new UserInfo{Name="oec2004",Age=21},
                new UserInfo{Name="oec2005",Age=22}
            };
            IEnumerable<UserInfo> selectedUser = from user in users
                                                 where user.Age > 20
                                                 orderby user.Age descending select user;
            foreach (UserInfo user in selectedUser)
            {
                Console.WriteLine("姓名:"+user.Name+",年龄:"+user.Age);
            }
            Console.ReadLine();
        }
    }

    可以看出,Linq可以让我们使用类似Sql的关键字来对集合、对象、XML等进行查询。 到这儿,C#3&NET3的一些特性也就说的差不多了,下面就看看C#4&NET4又有哪些变化吧。


    ★NET4随着VS2010一起发布,VS2010相比较VS2008有了很大的变化,有关VS2010的功能改进方面可以看我以前的博文:

    ★NET4 FramWork也有一些更新,比如提供了BigInteger、Complex、Tuple等,具体参见《Net4.0---Framwork新增特性》。

    ★在ASP.NET方面也有不少方便实用的特性,可以参考以下博文:

    在C#方面,比较典型的两个特性就是可选参数和命名实参,还有一个大的特性--动态类型。

    ★VB在很早就已经支持了可选参数,而C#知道4了才支持,顾名思义,可选参数就是一些参数可以是可选的,在方法调用的时候可以不用输入。看下面代码:

    public class Test 
    {
        static void Main()
        {
            Console.WriteLine(GetUserInfo()); //姓名:ooec2003,年龄:30 
            Console.WriteLine(GetUserInfo("oec2004", 20));//姓名:ooec2004,年龄:20 
            Console.ReadLine();
        }
        public static string GetUserInfo(string name = "oec2003", int age = 30)
        {
            return "姓名:" + name + ",年龄:" + age.ToString();
        }
    }

    ★命名实参是在制定实参的值时,可以同时指定相应参数的名称。编译器可以判断参数的名称是否正确,命名实参可以让我们在调用时改变参数的顺序。命名实参也经常和可选参数一起使用,看下面的代码:

    static void Main()
    {
        Console.WriteLine(Cal());//9 
        Console.WriteLine(Cal(z: 5, y: 4));//25 
        Console.ReadLine();
    }
    public static int Cal(int x=1, int y=2, int z=3)
    {
        return (x + y) * z;
    }

    ★动态类型,C#使用dynamic来实现动态类型,在没用使用dynamic的地方,C#依然是静态的。静态类型中当我们要使用程序集中的类,要调用类中的方法,编译器必须知道程序集中有这个类,类里有这个方法,如果不能事先知道,编译时会报错,在C#4以前可以通过反射来解决这个问题。看一个使用dynamic的小例子:

    dynamic a = "oec2003";
    Console.WriteLine(a.Length);//7 
    Console.WriteLine(a.length);//string 类型不包含length属性,但编译不会报错,运行时会报错 
    Console.ReadLine();

    有人可能会发现使用dynamic声明变量和C#3中提供的var有点类似,其他他们是有本质区别的,var声明的变量在编译时会去推断出实际的类型,var只是相当于一个占位符,而dynamic声明的变量在编译时不会进行类型检查。 dynamic用的比较多的应该是替代以前的反射,而且性能有很大提高。假设有一个名为DynamicLib的程序集中有一个DynamicClassDemo类,类中有一个Cal方法,下面看看利用反射怎么访问Cal方法:

    namespace DynamicLib
    {
        public class DynamicClassDemo 
        {
            public int Cal(int x = 1, int y = 2, int z = 3)
            {
                return (x + y) * z;
            }
        }
    }
    static void Main()
    {
        Assembly assembly = Assembly.Load("DynamicLib");
        object obj = assembly.CreateInstance("DynamicLib.DynamicClassDemo");
        Type type = obj.GetType();
        MethodInfo method = type.GetMethod("Cal");
        Console.WriteLine(method.Invoke(obj, new object[] { 1, 2, 3 }));//9 
        Console.ReadLine();
    }

    用dynamic的代码如下:

    Assembly assembly = Assembly.Load("DynamicLib");
    dynamic obj = assembly.CreateInstance("DynamicLib.DynamicClassDemo");
    Console.WriteLine(obj.Cal());
    Console.ReadLine();

    ★C#4中还有一些COM互操作性的改进和逆变性和协变性的改进,我几乎没有用到,所以在此就不讲述了。


    至此,所以内容已经完成,算是我对C#&NET1.0-4.0知识的一个梳理, 由于本人能力有限,本文只是我所涉及到的一小部分,随着我以后的学习,我会再开篇来记录新的学习体会。希望本文对你有所帮助。

  • 相关阅读:
    clip属性,截取你想要显示的图片
    select背景灰色
    [转]ShowModal() = mrOk
    【转】Delphi-Unit文件结构
    析构方法
    [转]Delphi 7连接MySql /MariaDB
    MySQL/Mariadb遇见的问题
    更换数据库
    FlameRobin
    FireBird初使用
  • 原文地址:https://www.cnblogs.com/oec2003/p/2741955.html
Copyright © 2020-2023  润新知