NHibernate体系文章七十五:NHibernate Mapping之Fluent Mapping基本功(附程序下载)

四、案例

写到这里我其实还想举一个例子来说说这种技巧在有些情况下是很常用的,大家在写EF配置,Automaper配置的时候经常这样写:

xx.MapPath(
                Path.For(_student).Property(x => x.Name),
                Path.For(_student).Property(x => x.Email),
                Path.For(_customer).Property(x => x.Name),
                Path.For(_customer).Property(x => x.Email),
                Path.For(_manager).Property(x => x.Name),
                Path.For(_manager).Property(x => x.Email)
                )

这样的写法就是前面的技巧改变而来,我们现在设计一个Validator,假如说这个Validator需要批量对Model的字段进行验证,我们也需要定义一个配置文件,配置某某Model的某某字段应该怎么样,利用这个配置我们可以验证出哪些数据不符合这个配置。

配置文件类Path的关键代码:

  public class Path<TModel>
    {
        private TModel _model;
        public Path(TModel model)
        {
            _model = model;
        }
        public PropertyItem<TValue> Property<TValue>(Expression<Func<TModel, TValue>> propertyExpression)
        {
            var item = new PropertyItem<TValue>(propertyExpression.PropertyName(), propertyExpression.PropertyValue(_model),_model);
            return item;
        }
    }

为了实现fluent,我们还需要定义一个静态非泛型类,

public static class Path
    {
        public static Path<TModel> For<TModel>(TModel model)
        {
            var path = new Path<TModel>(model);
            return path;
        }
    }

定义Validator,这个类可以读取到配置的信息,

  public Validator<TValue> MapPath(params PropertyItem<TValue>[] properties)
        {
            foreach (var propertyItem in properties)
            {
                _items.Add(propertyItem);
            }
            return this;
        }

最后调用

  [Test]
        public void Should_validate_model_values()
        {

            //Arrange
            var validator = new Validator<string>();
            validator.MapPath(
                Path.For(_student).Property(x => x.Name),
                Path.For(_student).Property(x => x.Email),
                Path.For(_customer).Property(x => x.Name),
                Path.For(_customer).Property(x => x.Email),
                Path.For(_manager).Property(x => x.Name),
                Path.For(_manager).Property(x => x.Email)
                )
              .OnCondition((model)=>!string.IsNullOrEmpty(model.ToString()));

            //Act
            validator.Validate();

            //Assert
            var result = validator.Result();
            result.Count.Should().Be(3);
            result.Any(x => x.ModelType == typeof(Student) && x.Name == "Email").Should().Be(true);
            result.Any(x => x.ModelType == typeof(Customer) && x.Name == "Name").Should().Be(true);
            result.Any(x => x.ModelType == typeof(Manager) && x.Name == "Email").Should().Be(true);
        }

这样的Fluent API语言更加清晰并且不失优雅,
Path.For(A).Property(x=>x.Name).OnCondition(B),这句话可以翻译为,对A的属性Name设置条件为B。

 2)新建Class
Library,名称为Demo.Fluent.Entities。移除Class1.cs文件。

  5.2 提高集合插入性能

Person p = new Person();
Publishing publishing = new Publishing();
//订阅新书发布事件
publishing.PublishingBook += p.Buy;
publishing.TrrigerBook();

二、设计具有调用顺序的Fluent API

在之前的例子中,API设计者期望使用者首先调用StartShow()方法来初始化一些数据,然后进行表演,最后使用者方可调用EndShow(),实现的思路是将不同种类的功能抽象到不同的接口中或者抽象类中,方法内部不再使用return
this,取而代之的是return INext;

根据这个思路,我们将StartShow(),和EndShow()方法抽象到一个类中,而将马戏团的表演抽象到一个接口中:

  public abstract class Performer
    {
        public abstract ICircusPlayer CircusPlayer { get;  }
        public abstract ICircusPlayer StartShow();
        public abstract void EndShow();
    }

    public interface ICircusPlayer
    {
        IList PlayedItem { get;  }
        ICircusPlayer MonkeysPlay();
        ICircusPlayer ElephantsPlay();
        Performer TogetherPlay();
    }

有了这样的分类,我们重新设计API,将StartShow()和EndShow()设计在CircusPerfomer中,将马戏团的表演项目设计在CircusPlayer中:

  public class CircusPerformer:Performer
    {
        private  ICircusPlayer _circusPlayer;

        override public ICircusPlayer CircusPlayer { get { return _circusPlayer; } }

        public override ICircusPlayer StartShow()
        {
            //make a speech and start to show
            _circusPlayer=new CircusPlayer(this);
            return _circusPlayer;
        }

        public override void EndShow()
        {
            //finish the show
        }
    }

  public class CircusPlayer:ICircusPlayer
    {
        private readonly Performer _performer;
        public  IList PlayedItem { get; private set; }

        public CircusPlayer(Performer performer)
        {
            _performer = performer;
            PlayedItem=new List();
        }

        public ICircusPlayer MonkeysPlay()
        {
            PlayedItem.Add("MonkeyPlay");
            //monkeys do some show
            return this;
        }

        public ICircusPlayer ElephantsPlay()
        {
            PlayedItem.Add("ElephantPlay");
            //elephants do some show
            return this;
        }

        public Performer TogetherPlay()
        {
            PlayedItem.Add("TogetherPlay");
            //all of the animals do some show
            return _performer;
        }
    }

这样的API可以满足我们的要求,在马戏团circusPerformer实例上只能调用StartShow()和EndShow()

图片 1

调用完StartShow()后方可调用各种表演方法。

图片 2

当然由于我们的API很简单,所以这个设计还算说得过去,如果业务很复杂,需要考虑众多的情形或者顺序我们可以进一步完善,实现的基本思想是利用装饰者模式和扩展方法,由于园子里的dax.net在很早前就发表了相关博客在C#中使用装饰器模式和扩展方法实现Fluent
Interface,所以大家可以去看这篇文章的实现方案,该设计应该可以说是终极模式,实现过程也较为复杂。

从这一节起,介绍NHibernate Mapping的内容。前面文章都是使用的NHibernate
XML Mapping。NHibernate XML
Mapping是NHibernate最早最成熟的Mapping方法。其他的Mapping方法都是基于XML
Mapping的思想进行的“变种”,要么暂时不能完全像XML
Mapping那样功能丰富。其他的Mapping方法目前包括:Fluent
Mapping、Attribute Mapping和Mapping by
Conventions。他们各自都有优缺点。使用者应该根据实际情况选择适合自己项目的Mapping方式。

7 扩展

View
Code

三、泛型类的Fluent设计

泛型类中有个不算问题的问题,那就是泛型参数是无法省略的,当你在使用var
list=new
List<string>()这样的类型时,必须指定准确的类型string。相比而言泛型方法中的类型时可以省略的,编译器可以根据参数推断出参数类型,例如

 var circusPerfomer = new CircusPerfomerWithGenericMethod();
            circusPerfomer.Show<Dog>(new Dog());
            circusPerfomer.Show(new Dog());

如果想省略泛型类中的类型有木有办法?答案是有,一种还算优雅的方式是引入一个非泛型的静态类,静态类中实现一个静态的泛型方法,方法最终返回一个泛型类型。这句话很绕口,我们不妨来看个一个画图板实例吧。

定义一个Drawing<TShape>类,此类可以绘出TShape类型的图案

public class Drawing<TShape> where TShape :IShape
    {
        public TShape Shape { get; private set; }
        public  TShape Draw(TShape shape)
        {
            //drawing this shape
            Shape = shape;
            return shape;
        }
    }

定义一个Canvas类,此类可以画出Pig,根据传入的基本形状,调用对应的Drawing<TShape>来组合出一个Pig来

  public void DrawPig(Circle head, Rectangle mouth)
        {
            _history.Clear();
            //use generic class, complier can not infer the correct type according to parameters
            Register(
                new Drawing<Circle>().Draw(head),
                new Drawing<Rectangle>().Draw(mouth)
                );
        }

这段代码本身是非常好懂的,而且这段代码也很clean。如果我们在这里想使用一下之前提到过的技巧,实现一个省略泛型类型且比较Fluent的方法我们可以这样设计:

首先这样的设计要借助于一个静态类:

   public static class Drawer
    {
        public static Drawing<TShape> For<TShape>(TShape shape) where TShape:IShape
        {
            return new Drawing<TShape>();
        }
    }

然后利用这个静态类画一个Dog

  public void DrawDog(Circle head, Rectangle mouth)
        {
            _history.Clear();
            //fluent implements
            Register(
                Drawer.For(head).Draw(head),
                Drawer.For(mouth).Draw(mouth)
            );
        }

可以看到这里已经变成了一种Fluent的写法,写法同样比较clean。写到这里我脑海中浮现出来了一句”费这劲干嘛”,这也是很多人看到这里要想说的,我只能说你完全可以把这当成是一种奇技淫巧,如果哪天遇到使用的框架有这种API,你能明白这是怎么回事就行。

展开工程的Reference,看到已经将FluentNHibernate添加进来了。

2.4 Switch语句

常常将Switch用作单一匹配,但不要忘记其多匹配功能,如下面的代码:

 

string sign ="b";
switch (sign)
{
     case "a":
     case "b":
           Console.WriteLine("多匹配方式");
         break;
     case "z":
         break;
     default:
         break;
}

还可以向下面这样,使用Switch和return组合。

    private int Switch()
     {
            int i = 0;
            switch (i)
            {
                case 0:
                    {
                        return 0;
                    }
                case 1:
                    {
                        return 1;
                    }
                default:
                    {
                        return 2;
                    }
            }
      }

继承规则

我们经常使用的一些框架例如:EF,Automaper,NHibernate等都提供了非常优秀的Fluent
Interface,
这样的API充分利用了VS的智能提示,而且写出来的代码非常整洁。我们如何在代码中也写出这种Fluent的代码呢,我这里介绍3总比较常用的模式,在这些模式上稍加改动或者修饰就可以变成实际项目中可以使用的API,当然如果没有设计API的需求,对我们理解其他框架的代码也是非常有帮助。

 1 namespace Demo.Fluent.Entities.Domain
 2 {
 3     public class Address
 4     {
 5         public virtual string Street { get; set; }
 6         public virtual string City { get; set; }
 7         public virtual string Province { get; set; }
 8         public virtual string Country { get; set; }
 9 
10         public bool Equals(Address other)
11         {
12             if (other == null) return false;
13             if (ReferenceEquals(this, other)) return true;
14             return Equals(other.Street, Street) &&
15                 Equals(other.City, City) &&
16                 Equals(other.Province, Province) &&
17                 Equals(other.Country, Country);
18         }
19 
20         public override bool Equals(object obj)
21         {
22             return Equals(obj as Address);
23         }
24 
25         public override int GetHashCode()
26         {
27             unchecked
28             {
29                 var result = Street.GetHashCode();
30                 result = (result * 397) ^ (City != null ? City.GetHashCode() : 0);
31                 result = (result * 397) ^ Province.GetHashCode();
32                 result = (result * 397) ^ Country.GetHashCode();
33                 return result;
34             }
35         }
36     }
37 }

  5.1 关于集合的标准查询运算符

有返回值的委托类型 

一、最简单且最实用的设计

这是最常见且最简单的设计,每个方法内部都返回return this;
这样整个类的所有方法都可以一连串的写完。代码也非常简单:

使用起来也非常简单:

   public class CircusPerformer
    {
        public List<string> PlayedItem { get; private set; }

        public CircusPerformer()
        {
            PlayedItem=new List<string>();
        }
        public CircusPerformer StartShow()
        {
            //make a speech and start to show
            return this;
        }
        public CircusPerformer MonkeysPlay()
        {
            //monkeys do some show
            PlayedItem.Add("MonkeyPlay");
            return this;
        }
        public CircusPerformer ElephantsPlay()
        {
            //elephants do some show
            PlayedItem.Add("ElephantPlay");
            return this;
        }
        public CircusPerformer TogetherPlay()
        {
            //all of the animals do some show
            PlayedItem.Add("TogetherPlay");
            return this;
        }
        public void EndShow()
        {
            //finish the show
        }

调用:

[Test]
        public void All_shows_can_be_invoked_by_fluent_way()
        {
            //Arrange
            var circusPerformer = new CircusPerformer();

            //Act
            circusPerformer
                .MonkeysPlay()
                .ElephantsPlay()
                .StartShow()
                .TogetherPlay()
                .EndShow();

            //Assert
            circusPerformer.PlayedItem.Count.Should().Be(3);
            circusPerformer.PlayedItem.Contains("MonkeysPlay");
            circusPerformer.PlayedItem.Contains("ElephantsPlay");
            circusPerformer.PlayedItem.Contains("TogetherPlay");
        }

但是这样的API有个瑕疵,马戏团circusPerformer在表演时是有顺序的,首先要调用StartShow(),其次再进行各种表演,表演结束后要调用EndShow()结束表演,但是显然这样的API没法满足这样的需求,使用者可以随心所欲改变调用顺序。

图片 3

如上图所示,vs将所有的方法都提示了出来。

我们知道,作为一个优秀的API,要尽量避免让使用者犯错,比如要设计private
字段,readonly
字段等都是防止使用者去修改内部数据从而导致出现意外的结果。

1、Fluent Mapping的优点

3 类

匿名对象的属性是只读,不可写的。匿名对象也可以调用Equals方法,与具名对象(有类型的对象)的同名方法不一样。该方法会测试该匿名对象的每个属性的值与另一个匿名对象的属性的值是否一致,如果为真则认为两个匿名对象是相等的,这可能是出于linq查询时的考虑,应特别注意这一点。 

类名称必须是值对象类型名称或实体类名称后面跟Map。

  2.1 foreach语句

如此,事件已经定义完毕。接着如何触发这个事件呢?我们知道.net
framework有它内置的触发事件机制,那是适用于窗体、按钮的内置触发机制,而我们自定义的事件则没有被规定如何触发,这需要我们自己用代码手动实现触发,发布新书自然是由出版社去执行,以下代码写在出版社这个类中:

在映射类的无参构造函数内,调用Fluent NHibernate的API函数,定义映射。

2 控制流语句

定义静态类

执行程序,得到结果(与数据库记录有关)。

5 集合

static void Main ( string [ ] args )
{
    //创建委托实例,提供函数标识符
    Calculator c = new Calculator ( Show );
    //调用函数
    c ( 1 , 2 );

 1 namespace Demo.Fluent.Entities.Domain
 2 {
 3     public abstract class Entity<T> where T : Entity<T>
 4     {
 5         public virtual int Id { get; private set; }
 6 
 7         public override bool Equals(object obj)
 8         {
 9             var other = obj as T;
10             if (other == null) return false;
11             var thisIsNew = Equals(Id, 0);
12             var otherIsNew = Equals(other.Id, 0);
13             if (thisIsNew && otherIsNew)
14             {
15                 return ReferenceEquals(this, other);
16             }
17             return Id.Equals(other.Id);
18         }
19 
20         private int? oldHashCode;
21         public override int GetHashCode()
22         {
23             // once we have a hashcode we'll never change it
24             if (oldHashCode.HasValue)
25             {
26                 return oldHashCode.Value;
27             }
28             // when this instance is new we use the base hash code
29             // and remember it, so an instance can NEVER change its
30             // hash code.
31             var thisIsNew = Equals(Id, 0);
32             if (thisIsNew)
33             {
34                 oldHashCode = base.GetHashCode();
35                 return oldHashCode.Value;
36             }
37             return Id.GetHashCode();
38         }
39 
40         public static bool operator ==(Entity<T> lhs, Entity<T> rhs)
41         {
42             return Equals(lhs, rhs);
43         }
44         public static bool operator !=(Entity<T> lhs, Entity<T> rhs)
45         {
46             return !Equals(lhs, rhs);
47         }
48     }
49 }

3.5默认初始值

字段或属性默认初始值随类型的不同而不同。

bool默认初始值为false,对象类型默认初始值为null,int类型为0,float和double为0.0,char为。

键入ctor
连续按tab键两次,自动生成构造器。

using Demo.Fluent.Entities.Domain;
using FluentNHibernate.Mapping;

namespace Demo.Fluent.Entities.Mapping
{
    public class ProductMap : ClassMap<Product>
    {
        public ProductMap()
        {
            Id(x => x.ID).GeneratedBy.Native();
            Map(x => x.ProductCode).Not.Nullable().Length(10);
            Map(x => x.ProductName).Not.Nullable().Length(50);
            Map(x => x.Description).Length(100);
            HasManyToMany(x => x.Orders).Table("ProductOrder").ParentKeyColumn("ProductId").ChildKeyColumn("OrderId").Cascade.AllDeleteOrphan();
        }
    }
}

  2.2 if-else语句

最终,我们发现事件的定义非常简单,总结起来就三点:

 

内容摘要

static void Test(string name,  int age=32 ) //如果没有传递age,则使用默认值,否则覆盖默认值
{
           
}

3)在新建的工程中,使用NuGet安装FluentNHibernate。

3.11 接口中的属性

在接口中定义属性,若只包含Get块,那么接口的实现类中也只能包含Get块。

例如:

  public class Child : Face
    {
        public string Field
        {
            get { throw new NotImplementedException(); }
        }
    }
    public interface Face
    {
        string Field
        {
            get;
        }
  }

 

2.数据类型(返回的类型)

9)添加用于NHibernate设置的FluentConfig类。

 

图片 4图片 5

 1 using Demo.Fluent.Entities.Domain;
 2 using NHibernate.Linq;
 3 using System.Linq;
 4 
 5 namespace Demo.Fluent.Console
 6 {
 7     class Program
 8     {
 9         static void Main(string[] args)
10         {
11             var factory = FluentConfig.CreateSessionFactory();
12             using (var session = factory.OpenSession())
13             {
14                 var customer = session.Get<Customer>(2);
15                 System.Console.WriteLine("{0} {1}", customer.Name.LastName, customer.Name.FirstName);
16 
17                 System.Console.WriteLine("order count: {0}",customer.Orders.Count());
18 
19                 System.Console.WriteLine();
20                 System.Console.WriteLine("customers and their order count:");
21                 var queryCount = session.Query<Customer>().Select(c => new
22                 {
23                     CustomerId = c.Id,
24                     CustomerName = c.Name.FirstName + " " + c.Name.LastName,
25                     Count = c.Orders.Count()
26                 });
27                 var listCount = queryCount.ToList();
28                 if (listCount.Count > 0)
29                 {
30                     listCount.ForEach(o =>
31                     {
32                         System.Console.WriteLine("{0}-{1}: {2}", o.CustomerId, o.CustomerName, o.Count);
33                     });
34                 }
35 
36                 System.Console.WriteLine();
37 
38                 System.Console.WriteLine("customers whose oders count greater than 2:");
39                 var queryCountGreater = session.Query<Customer>().Where(c => c.Orders.Count > 2);
40                 var listCountGreater = queryCountGreater.ToList();
41                 if (listCountGreater.Count > 0)
42                 {
43                     listCountGreater.ForEach(o =>
44                     {
45                         System.Console.WriteLine("{0}-{1} {2}", o.Id, o.Name.FirstName, o.Name.LastName);
46                     });
47                 }
48             }
49             System.Console.WriteLine();
50             System.Console.WriteLine("Finished");
51             System.Console.ReadLine();
52         }
53     }
54 }

  1.2 数组的长度问题

新书要有自己的书籍信息,因为它是事件信息,按照官方的要求,这种事件信息类必须派生自EventArgs。

一对多:HasMany

5.1 关于集合的标准查询运算符

1)First, FirstOrDefault,Single,SingleOrDefault

First:查找第一个符合条件的元素,如果没有找到,抛异常。

FirstOrDefault:查找第一个符合条件的元素,如果没有找到,返回null。

Single:找到符合条件的一个元素,如果找不到,抛异常;如果有多个元素符合条件,抛异常。

SingleOrDefault:找到符合条件的一个元素,如果找不到,返回null;如果有多个元素符合条件,抛异常。

结论:

如果集合中可能只有一个或没有符合条件的元素,用FirstOrDefault和SingleOrDefault都可,不建议用First和Single,因为要自己处理异常。

如果集合中可能有多个或没有符合条件的元素,建议使用FirstOrDefault。

2)Select

Select为将集合中的元素“映射”为其他形式,而不是筛选出符合某一条件的元素。

3)Except<T>

假设有两个类型为List<string>集合,list1和list2。

list1.Except(list2);返回结果为从list1中去掉list1和list2相同的元素后剩余的部分;

list2.Except(list1);返回结果为从list2中去掉list1和list2相同的元素后剩余的部分;

若要求两个集合相同的部分,方法为:

List<string> list3 = list1.Except(list2);

List<string> same = list1.Except(list3);

若要求两个集合的并集且无重复,方法为:

List<string> listA = list1.Except(list2);

List<string> listB = list2.Except(list1);

List<string> all = listA.AddRange(listB);

图片 6图片 7

 1 using System;
 2 
 3 namespace Demo.Fluent.Entities.Domain
 4 {
 5     public class Name
 6     {
 7         public string LastName { get; private set; }
 8         public string FirstName { get; private set; }
 9 
10         public Name() { }
11 
12         public Name(string firstName, string lastName)
13         {
14             if (string.IsNullOrWhiteSpace(firstName))
15             {
16                 throw new ArgumentException("First name must be defined.");
17             }
18             if (string.IsNullOrWhiteSpace(lastName))
19             {
20                 throw new ArgumentException("Last name must be defined.");
21             }
22             FirstName = firstName;
23             LastName = lastName;
24         }
25 
26         public override int GetHashCode()
27         {
28             unchecked
29             {
30                 var result = FirstName.GetHashCode();
31                 result = (result * 397) ^ LastName.GetHashCode();
32                 return result;
33             }
34         }
35 
36         public bool Equals(Name other)
37         {
38             if (other == null) return false;
39             if (ReferenceEquals(this, other)) return true;
40             return Equals(other.FirstName, FirstName) &&
41                 Equals(other.LastName, LastName);
42         }
43 
44         public override bool Equals(object other)
45         {
46             return Equals(other as Name);
47         }
48     }
49 }

  3.7 构造函数的调用问题

//出版社
public class Publishing
{
    //事件拥有者必须拥有规范事件的委托,因为委托是中间角色,外部订阅事件时需要订阅到委托上去。
    private OderEventHandler o;
}

AddressMap类

 

View
Code

图片 8

3.1 配置文件中使用静态字段或静态属性

静态变量是在静态变量所属类初次使用时被初始化的,当静态字段被初始化后,之后每次调用获得的值都是初始化时赋给静态字段的值,除非在这个过程中显示地给静态字段赋值。而静态属性的某些行为类似于静态方法。如下例:

    public class Sys
    {
        /// <summary>
        /// 执行时间
        /// </summary>
        public static string ExecuteTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

        public static string ExecuteDate
        {
            get
            {
                return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            }
            set { }
        }
    }
     static void Main(string[] args)
        {
            Console.WriteLine("字段:"+Sys.ExecuteTime);

            Console.WriteLine("属性:"+Sys.ExecuteDate);

            Thread.Sleep(2000);

            Console.WriteLine("字段0:" + Sys.ExecuteTime);

            Console.WriteLine("属性0:" + Sys.ExecuteDate);

            Thread.Sleep(2000);
            Sys.ExecuteDate = DateTime.Now.ToString("yyyy");
            Console.WriteLine("字段1:" + Sys.ExecuteTime);

            Console.WriteLine("属性1:" + Sys.ExecuteDate);
            Console.Read();

        }

运行上述代码,输出结果如下:

图片 9

由输出结果可以得出:上面的用法中,静态字段每次调用获得的值都是同一个,即初始化时所赋的值;而调用静态属性每次获得的值都是不同的,每次调用都执行一次get方法。

事件的定义需要实现add(事件添加器)和remove(事件移除器)。其中value就代表了上面代码中的
p.Buy。

  • HasManyToMany方法生成ManyToManyPart对象,映射Many-to-Many关系。
  • Table(“ProductOrder”).ParentKeyColumn(“ProductId”).ChildKeyColumn(“OrderId”)
  • 上面连串方法调用定义了Many-to-Many映射的中间表表名。对于Product表,这个关系的主键列和外键列。
  • 多对多关系映射默认生成的中间表的名称是ProductToOrder。因此,这里要调用Table方法,用字符串定义中间表名称。

2.3 for循环

一般循环的循环变量为int型,但是其他类型如float,double等也是可以使用的。

接口(Interface)

这里有六个需要注意的地方:

  3.6 readonly

//也即a存储的是p指向的地址(包含内存中Animal对象的地址和自身的地址),但不能通过a去访问地址指向的p的内容,但可以访问内存中的Animal对象的内容。

4)添加Domain文件夹和Mapping文件夹。

  3.10尽量使用属性而不是字段

构造函数在类型中定义,是类型的成员,new操作符正是调用构造函数来为对象做初始化工作的。名称与类名完全一样的函数被视为构造函数,构造函数没有返回值,也不能return。

结语

  6.2 try…finally与return

方法的优势就在于可重复使用,为了达到重复使用同时又能降低今后修改方法内部逻辑的工作量,我们就要做到对方法的逻辑实现必须遵循复用这一基本原则,比如现在假设需要写三个方法分别计算圆面积、圆柱体积和圆锥体积

图片 10

3.6 readonly

readonly只能用于字段,它指明被其修饰的字段只能在构造函数中修改,或在声明时指定。但对于数组来说情况稍有不同,使用此修饰符修饰数组,那么不允许使用new运算符创建同一个数组的新实例,但可以修改数组中的元素,即使这样的操作不是在构造函数中进行的。

派生类可以获得基类的成员,比如基类是Animal,它有EatFood方法,它有两个派生类Cow和Chicken,它们通过继承自动获得EatFood方法:  

  • Address类和Name类都时值对象类,因此他们的映射类文件都继承ComponetMap的泛型类。
  • Map、Not.Nullable、Length都时Fluent
    NHibernate的API函数(见名知意),通过链式调用,对单个属性进行映射定义。

2 控制流语句

图片 11图片 12

 1 using System;
 2 using System.Collections.Generic;
 3 
 4 namespace Demo.Fluent.Entities.Domain
 5 {
 6     public class Customer : Entity<Customer>
 7     {
 8         public Customer()
 9         {
10             MemberSince = DateTime.UtcNow;
11         }
12 
13         public virtual Name Name { get; set; }
14         public virtual double AverageRating { get; set; }
15         public virtual int Points { get; set; }
16         public virtual bool HasGoldStatus { get; set; }
17         public virtual DateTime MemberSince { get; set; }
18         public virtual CustomerCreditRating CreditRating { get; set; }
19         public virtual Address Address { get; set; }
20 
21         private readonly IList<Order> orders;
22 
23         public virtual IList<Order> Orders
24         {
25             get
26             {
27                 return orders;
28             }
29         }
30     }
31 
32     public enum CustomerCreditRating
33     {
34         Excellent, VeryVeryGood, VeryGood, Good, Neutral, Poor, Terrible
35     }
36 }

  2.3 for循环

该类不能new,可继承。可以这样理解:抽象的东西是无形之物,因为其无形,所以无法new出具体的样貌,但具体的事物可以从其派生,因为具体的事物全部源自无形的虚空。

 1 using Demo.Fluent.Entities.Domain;
 2 using FluentNHibernate.Mapping;
 3 
 4 namespace Demo.Fluent.Entities.Mapping
 5 {
 6     public class CustomerMap : ClassMap<Customer>
 7     {
 8         public CustomerMap()
 9         {
10             Id(x => x.ID).GeneratedBy.Native();
11             Component(x => x.Address);
12             Component(x => x.Name);
13             Map(x => x.Points);
14             Map(x => x.HasGoldStatus);
15             Map(x => x.MemberSince);
16             Map(x => x.CreditRating).CustomType<CustomerCreditRating>();
17             HasMany(x => x.Orders).Inverse().Cascade.AllDeleteOrphan().Fetch.Join();
18         }
19     }
20 }

  3.4 对比virtual与abstract

类内部可访问,派生类内部可访问。可组合Internal。

  • Fluent Mapping提供了大量的Fluent API进行映射配置。相比XML
    Mapping,在代码中进行配置能够在编译时发现很多问题。
  • Fluent Mapping的可读性更强,代码更简洁。
  • Fluent
    Mapping将映射配置的类和实体映射类相分离,在一定程度上保持了实体类的简洁性。
  • Fluent
    Mapping使用Lamda表达式和静态类型反射技术,不用写大量常量字符串,避免了很多粗心的错误。

1.4 匿名类型与隐式类型

C#的匿名类型没有名称,是由编译器动态生成的数据类型,但它仍然是强类型。对匿名类型来说,不可能指定数据类型,所以声明匿名类型变量要使用var。

使用var来声明隐式类型。但对于数据类型并非匿名类型的情况下,建议使用显示数据类型。

var anonymous1 = new { Field1 = “sss”, Field2 = “bbb” };

var anonymous2 = new { Field1 = “sss”, Field2 = “CCC” };

var anonymous3 = new { Field1 = “ttt” };

var iy = “string”;

无法将anonymous1与anonymous3互相赋值,无法将nim 与iy互相赋值,但anonymous1与anonymous2可以互相赋值

 

继续简化事件的定义

  • 在定义了实体类之后,需要另外定义一个实体映射类。
  • 许多XML Mapping支持的功能,Fluent Mapping暂时不支持,需要等到Fluent
    Mapping新版本出来后才能支持。
  • Fluent
    Mapping底层其实还是将代码定义的映射翻译成XML映射文件,因此在程序启动的时候比XML
    Mapping稍慢。
  • 如果数据库表名称和实体类名称不一致,或者数据库列名称和属性名称不一致,还是需要用字符串的形式做映射,这基本是避免不了的。

6.1 优先考虑在最外层捕获异常

假设FunctionA,FunctionB,FunctionC三个方法,FunctionA调用了FunctionB,FunctionB调用了FunctionC,那么首先考虑对FunctionA使用try…catch语句。

public class Animal
{
    public string name;
    public Animal(string xName)
    {
        name = xName;
    }
}
Animal a = new Animal ( “蚂蚁” );
//流程:初始化Animal对象,分配内存,扫描它的成员字段name并初始化它,然后进入构造函数代码体,代码体存在一段为name字段赋新值的代码,执行后退出。

 1 using Demo.Fluent.Entities.Domain;
 2 using FluentNHibernate.Mapping;
 3 
 4 namespace Demo.Fluent.Entities.Mapping
 5 {
 6     public class AddressMap : ComponentMap<Address>
 7     {
 8         public AddressMap()
 9         {
10             Map(x => x.Street).Length(100);
11             Map(x => x.City).Length(100);
12             Map(x => x.Province).Length(100);
13             Map(x => x.Country).Length(100);
14         }
15     }
16 }

  6.1 优先考虑在最外层捕获异常

//事件拥有者与事件订阅者是它自身。
namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Form form = new Form();
            form.Click += new EventHandler(form.ResponseClick);           
            form.ShowDialog();
        }
    }

    public static class FormExtension
    {
        public static void ResponseClick(this Form form,object o,EventArgs e)
        {
            Form f = (Form)o;
            f.Text = DateTime.UtcNow.ToString();
        }
    }
}

Order类

4 泛型

构造函数重载

一对一:HasOne

  3.5默认初始值

C#中所有类型最开始都从Object派生,结构从System.ValueType派生,System.ValueType从Object派生。结构被隐式地定义为sealed(不可从其派生)。内置值类型都是预定义的结构类型(struct),所以值类型也都是隐式地被new出来的,你也可以尝试显示地new出一个int(调用int的结构构造函数初始化为0)。也可以自定义自己的值类型,结构的定义与类、接口类似,但关键字不是class、interface,而是struct。结构是一种存储在栈上的值类型,虽然它不是引用类型。但它可以包含类能包含的所有成员。结构隐式地被定义为sealed,所以不能从它派生。微软引入结构是因为引用类型容易消耗大量内存,而结构存储在栈上,这个单一的数据结构比单一的值类型数据占用更多的内存栈空间,也就可以存储更多的数据,而结构不是引用类型,所以在保证了存储更多数据的情况下又没有引用类型那么大的内存消耗,结构就显得更为轻量级。结构和类一样具有默认的无参构造函数,这个构造函数被隐式地调用以便初始化结构的成员。也可以显示定义结构的构造函数,但不能定义无参版的构造函数,因为结构默认的无参构造函数不会因为你定义了有参构造函数就被覆盖掉,这区别与类的构造函数(类的自定义构造函数会覆盖默认无参构造函数)。结构的默认构造函数不会被覆盖是为了防止在创建结构实例时你忘记初始化成员。当然,你也可提供重载版构造函数来初始化结构的成员。也即,假如你定义了有参构造函数,那么在创建结构实例时,编译器会提醒你构造函数存在重载版,你可以调用无参构造函数,它会扫描成员并自动初始化成员,也可以调用自定义的有参构造函数,如果调用的是有参构造函数,那么就要确保该构造函数中为每一个成员都做了初始化工作,否则会提示编译错误。C#强迫为结构的字段成员做初始化工作只能在构造函数中进行,而属性成员如果定义了get,set,但无任何实现,那么就需要在自定义的构造函数中初始化它,如果它已经实现了get、set,那么可以不为其做初始化工作,但无论如何字段都必须被初始化。最后,结构类型就是一个值类型的聚合,因为它本身是一个聚合的类型,所以不要试图把结构类型定义在类中,因为结构定义在类中会被视为嵌套类,没有太大意义。但你可以在类中定义结构类型的成员字段、属性。

  • Table方法定义映射的表名称,因为Order是SQL
    Server关键字,因此调用此方法,传入字符串”`Order`”作为表名称。生成的SQL语句的表名称字符串是”[Order]”。
  • Many-to-One关系,实体类属性用Reference方法定义,指定外键列名称。 
  • 在ProductMap类构造函数内已经定义了中间表名称。因此,这里的Table(“ProductOrder”)可以省略。

1.1表达范围问题

int类型只能表达-232 至232 -1范围内的数据,float和double比int范围要宽的多,所以在使用这些类型时注意挑选合适的类型使用,另外与金融计算相关时使用decimal。

public class Animal
{
    public int GetCount( )
    {
        return 100000;
    }
}

 1 using System;
 2 using System.Collections.Generic;
 3 
 4 namespace Demo.Fluent.Entities.Domain
 5 {
 6     public class Order : Entity<Order>
 7     {
 8         public virtual DateTime Ordered { get; set; }
 9         public virtual DateTime? Shipped { get; set; }
10         public virtual Address ShipTo { get; set; }
11         public virtual Customer Customer { get; set; }
12 
13         private readonly IList<Product> products;
14 
15         public virtual IList<Product> Products
16         {
17             get
18             {
19                 return products;
20             }
21         }
22     }
23 }

3.4 对比virtual与abstract

为支持重写,基类中必须为要在子类中重写的成员之前添加virtual修饰符,子类成员要标记为override。

使用abstract定义抽象方法。抽象方法没有具体实现,必须在子类方法中实现抽象方法。

虚方法是可以有具体实现的,不过具体实现会在子类的重载中被覆盖。

public class GetCircleMsg
{
    //圆面积
    public double GetCircleArea( double r )
    {
        return 3.14 * r * r;
    }
    //圆柱体积
    public double GetCylinVolume( double r , double h )
    {
        return 3.14 * r * r * h;
    }
    //圆锥体积
    public double GetConeVolume( double r , double h )
    {
        return 3.14 * r * r * h / 3;
    }
}

3、程序演示

6.2 try…finally与return

finally块中的语句总会执行,除了finally语句块中的语句抛异常以外。

 

       try 
            {
                Console.WriteLine("Execute try");
                return;
            }
            catch {
                return;
            }
            finally
            {
                Console.WriteLine("Execute finally");
            }

 

输出:

图片 13

图片 14图片 15

摘要

3.2 虚方法中不要含有业务逻辑

使用virtual修饰符修饰类的方法,那么这个方法就可以在派生类中重写,如果原来的方法包含业务逻辑,派生类重写这个方法后,由于派生类将父类中的虚方法完全覆盖,导致虚方法中的业务逻辑永远不会被执行。

1.常量字段属于类成员,所以可以将它看成是隐式静态(Static),所以在类内部实例成员可以访问常量,而只读字段属于类还是对象取决于是否有static做形态修饰。

  • Customer类继承泛型类Entity<Customer>。
  • 不用再在Customer类里定义Id属性。
  • 必须有一个无参数的构造函数,可以在这个构造函数中定义属性的默认值。
  • 集合属性类型必须定义成接口类型,Fluent
    NHibernate通过反射生成Fluent对应的集合类型。
  • 不能在构造函数中对集合属性进行初始化。
  • 所有成员函数(如果有的话)和成员属性都以virtual修饰。

6 异常处理

定义方法  

这篇文章介绍Fluent
Mapping。本篇文章的代码可以到Fluent
NHibernate下载。

3.9 可访问性

非嵌套类型:不嵌套在其他类型中的顶级类型的可访问性只能是
internal 或 public。 这些类型的默认可访问性是 internal。

嵌套类型:嵌套类型的可访问性取决于它的可访问域,该域是由已声明的成员可访问性和直接包含类型的可访问域这二者共同确定的。
但是,嵌套类型的可访问域不能超出包含类型的可访问域。

属于

默认的成员可访问性

该成员允许的声明的可访问性

enum

public

class

private

public

protected

internal

private

protected internal

interface

public

struct

private

public

internal

private

注:

1)访问修饰符internal:只有在同一程序集的文件中,内部类型或成员才是可访问的。 

访问修饰符protected internal:访问仅限于从包含类派生的当前程序集或类型。

常量字段:const

图片 16

  3.3 指定构造器

View Code

 1 using Demo.Fluent.Entities.Domain;
 2 using FluentNHibernate.Mapping;
 3 
 4 namespace Demo.Fluent.Entities.Mapping
 5 {
 6     public class NameMap : ComponentMap<Name>
 7     {
 8         public NameMap()
 9         {
10             Map(x => x.LastName).Not.Nullable().Length(10);
11             Map(x => x.FirstName).Not.Nullable().Length(10);
12         }
13     }
14 }

  3.9 可访问性

类(Class)

图片 17

4 泛型

public class Myclass : BaseClass
{
    public Myclass( string name ) : base ( name ) //转接到基类带参构造函数上
    {
        Console.WriteLine ( “派生类构造函数被执行” );
    }
}

点击“OK”按钮。等上几分钟时间去喝口茶, 安装完成之后Output将显示Finished。

  1.5硬编码造成的精度丢失问题

用于定义成员的访问权限。

2、Fluent Mapping的缺点

  3.11 接口中的属性

public class Programe
{
    static void Main( string [ ] args )
    {
        Animal a = new Animal( ); //流程:初始化Animal对象,分配内存,扫描它的成员字段name并初始化它,然后进入构造函数代码体,代码体无可执行语句,退出。
        Console.WriteLine( a.Name == null ); //print true
        Console.WriteLine (a.FoodCount == 0); //print true
    }

5)在Domain文件夹内,添加实体类的抽象泛型基类Entity。

2.1 foreach语句

使用foreach语句操作集合,禁止循环操作过程中修改集合中的元素。

 

try
{
  List<string> list = new List<string> { "first", "second", "administrator", "letter",         "join" };
  foreach (var item in list)
  {
    if (item.Contains("l")) list.Remove(item);
  }
}
catch (Exception ex)
{
  Console.WriteLine(ex.Message);
}

 

异常信息:集合已修改;可能无法执行枚举操作。

异常类型:System.InvalidOperationException

图片 18图片 19

Name类

5.2 提高集合插入性能

对于List<T>:

如果集合大小已经小于集合默认容量,此方法为复杂度为
o (1) 操作。如果需要增加以容纳新元素的容量,此方法将变为
O (n) 操作,其中 n 是数组大小。

对于LinkedList:

LinkedList.AddFirst(),AddLast()的运算复杂度为 O(1)。

对于Stack和Queue:

如果集合大小已经小于集合默认容量,此方法为复杂度为
o (1) 操作。如果需要增加以容纳新元素的容量,此方法将变为
O (n) 操作,其中 n 是数组大小。

结论:如果能事先确定大小,则确定大小。

定义事件的简约写法

虽然Fluent NHibernate目前还不是很成熟(比起XML
Mapping来说),但是绝大部分Mapping功能都已经可以能满足了。前面提过了他的优缺点,有兴趣的可以到Fluent
NHibernate官网http://www.fluentnhibernate.org上去查看更详细的内容。

2.2 if-else语句

if(condition1){}

else if(condition2){}

else if(condition3){}

else{}

与之等价的写法更好理解。

if(condition1)

else

{

  if(condtion2)

  else

  {

    if(condition3){}

    else{}

  }

}

3.方法 

Product类

1 数据类型

static void Main( string [ ] args ) 
{
    string [ ] f = { “番茄” , “荔枝”,”芒果” };           
    Test ( name:”生物”  );   
}

ProductMap类

1 数据类型

通过派生类的构造函数访问base代表的基对象的Name字段并初始化它。  

  • 抽象基类Entity定义了实体类共有的Id属性。
  • 抽象基类Entity重写了object类的Equals方法和GetHashCode方法,同时重载了运算符==和!=。

1.3 值类型与引用类型

string为引用类型,但下面的方法不会改变其自身:

string str =”abc”;

str.ToUpper();

上面的代码不会将其转为大写,下面写法可行:

str = str.ToUpper();

public class Person
{
    private string userName; //通常做法是不向外界暴露字段,通过在类内部用属性访问字段。
    public string Name
    {
        get
        {
            return userName == “” ? “匿名用户” : userName;
        }
        set
        {
            userName = value == “” ? “匿名用户” : value;
        }
    }
}

继续使用以之前文章使用过的NHibernateDemoDB数据库。

3.10尽量使用属性而不是字段

可以对属性进行更灵活的控制,所以应尽量使用属性,例如下面的代码:

     //只有在类内部可以设置属性值
        public string FileName { private set; get; }

        //可以将计算步骤放在get中
        public int FileSize 
        {
            get 
            {
                int size = 0;
                return size;
            }
        }

 

定义  

关系映射的API方法:

4.1string.Join方法不能识别泛型

string JoinStr<T>(T set)

{

  Return string.Join(“,”,set);

}

上面这个方法不能正确返回拼接后的字符串,正确的方式如下:

string JoinStrRight(IEnumerable<string>  set)

{

  Return string.Join(“,”,set);

}

//泛型自身被视为一种类型,而尖括号里的T被称为形参变量,T相当于是一个占位符,它表示某种任意的类型    
public class Template<T> { } //读作:Template for T类型
//只有在定义泛型的时候,才可以定义泛型形参,每个形参以逗号隔开
public class Template2<T,V> { }
//在泛型类内部,任何位置处都能使用泛型形参作为成员、变量的类型
public class Template3<T, V, Key>
{
    public T person;
    public V favit;
    public Key numberCode;
    public void GetCount(T xPerson)
    {
        person = xPerson;
    }
}

Address类

3.3 指定构造器

为了避免因缺少可供访问的默认构造器而造成错误,要在派生类构造器的头部显示指定要运行哪一个基类构造器。

在类外部,类的成员只能由类访问,对象的成员只能由对象访问,类不能访问对象的成员,对象也不能去访问类的成员。

9)修改Program类。

  2.4 Switch语句

图片 20图片 21

图片 22

  1.4 匿名类型与隐式类型

自动属性

Customer类

  7.1对System.Linq的行为进行扩展

规范

7)在Mapping文件夹下,定义映射类AddressMap、NameMap、CustomerMap、ProductMap和OrderMap。

5 集合

成名重名

 NameMap类

  1.1表达范围问题

1.new
操作符

这里写了三个查询用来测试。第一个查询是通过Id查找Customer对象。第二个查询使用Linq
to
NHibernate对Customer和订单数量分组查询。第三个查询查找订单数大于2的Customer信息。

  3.1 配置文件中使用静态字段或静态属性 6

属性与字段的区别

 1 using FluentNHibernate.Mapping;
 2 
 3 namespace Demo.Fluent.Entities.Domain
 4 {
 5     public class OrderMap : ClassMap<Order>
 6     {
 7         public OrderMap()
 8         {
 9             Table("`Order`");
10             Id(x => x.ID).GeneratedBy.Native();
11             Map(x => x.Ordered);
12             Map(x => x.Shipped);
13             Component(x => x.ShipTo);
14             References(x => x.Customer).Column("CustomerId").Cascade.SaveUpdate();
15             HasManyToMany(x => x.Products).Table("ProductOrder").ParentKeyColumn("OrderId").ChildKeyColumn("ProductId").Cascade.All();
16         }
17     }
18 }

1.5硬编码造成的精度丢失问题

 

如果输入的数字字面值是含有小数,那么计算时默认为double类型,不含有小数,则认为是int类型;以f,d,m结尾的数被认为是float,double,decimal。所以涉及的相关运算时,注意写法。例如:

 

//运算结果为1.0

float res = 3 / 2;

 

//运算结果为1.5

float ress = 3f / 2f;

 

//这是错误写法,因为2.2这种写法是一个double类型

float f = 2.2;

 

//最后一个字符为f或F则表示float类型

float ff = 2.2f;

 

//这是错误写法,因为2.2是double类型,所以运算结果为double类型,无法将double类型隐式转换为float类型

//强制转换可以,但是可能会造成精度丢失。

float fff = 1 / 2.2;

事件订阅的不同写法

图片 23

  3.2 虚方法中不要含有业务逻辑

View
Code

1)新建工程Demo.Fluent。

  1.3 值类型与引用类型

View
Code

单击“Install”按钮,会出现Priview对话框,列出将要添加的引用。安装FluentNHibernate将会安装他所依赖的NHibernate和Isesi.Collections。

7 扩展

出版社应该有一个名字: 

OrderMap类

1.2 数组的长度问题

定义一个数组时最大的长度是多少?理论上最大长度为int.MaxValue。对于32位有符号整数来说最大值为2147483647,64位有符号整数最大值为9223372036854775807。一般来讲不会定义太长的数组,因为这样会比较消耗内存。

 

public class BaseClass
{
    public BaseClass( ) { Console.WriteLine ( “基类无参构造函数被执行” ); }
    public BaseClass( string name ) { Console.WriteLine ( “基类带参构造函数被执行” ); }
}

多对对:HasManyToMany

  3.8 静态类和静态成员

静态构造函数禁止指定权限修饰符,它作为类的成员只会被执行一次,也即当用Animal类访问静态成员时,静态构造函数会自动执行,但当再次访问静态成员时,静态构造函数就不会再执行了,这是因为类成员不同于对象成员,对象成员的所有权归对象所有,每创建一个对象都会在当前对象所在的内存区块中创建出对象的成员,多个对象之间各自维护着自己的成员,而类成员的所有权归属于类,是属于当前类的全局成员,只会在内存中创建一次,也即静态构造函数只可能被执行一次,在内存中为类创建好其成员后就不会再次执行。 

  • Customer类是实体类,继承ClassMap的泛型类。
  • Id方法定义主键属性,调用Generate.Native()方法指出主键生成策略是indentity的。
  • 对值对象类型的属性,调用Component方法,定义映射。
  • HasMany方法生成OneToManyPart对象,映射一对多关系。
  • HasMany方法调用后面的一串方法:Cascade.AllDeleteOrphan().Fetch.Join()对应了XML映射响应的属性。

  4.1string.Join方法不能识别泛型

C#支持名称重复的方法名,重名的方法称为方法重载。但名称重复的方法必须具有不同的方法签名。

 1 using Demo.Fluent.Entities.Mapping;
 2 using FluentNHibernate.Cfg;
 3 using FluentNHibernate.Cfg.Db;
 4 using NHibernate;
 5 
 6 namespace Demo.Fluent.Console
 7 {
 8     class FluentConfig
 9     {
10         const string connString = "server=localhost;" + "database=NHibernateDemoDB;" + "integrated security=SSPI;";
11 
12         public static ISessionFactory CreateSessionFactory()
13         {
14             return Fluently.Configure()
15                 .Database(MsSqlConfiguration.MsSql2008.ConnectionString(connString))
16                 .Mappings(m => m.FluentMappings.AddFromAssemblyOf<CustomerMap>())
17                 .BuildSessionFactory();
18         }
19     }
20 }

7.1对System.Linq的行为进行扩展

扩展方法:

 

public static class Extend
{
        public static string ToString(this string input, int str)
        {
            return "输入的是:" + str;
        }
}

 

Main方法中调用:

using System;

namespace ConsoleApp_CSharp
{
    class Program
    {
        static void Main(string[] args)
        {
            string str = "输入";
            Console.WriteLine(str.ToString(1));
            Console.Read();
        }

    }
}

编译Main方法,无法编译通过,错误信息如下:

图片 24

原因是:未引入扩展方法所在命名空间。在调用扩展方法时,引入扩展方法的命名空间即可,即使用如上例中使用将using
ConsoleApp_CSharp.Extend;语句引入。

 

事件的4种情形

CustomerMap类

3.7 构造函数的调用问题

如果实例化一个子类,那么子类的构造函数及其父类的构造函数的调用过程是怎么样的呢?调用具有怎样的层次关系?如果父类有几个重载的构造函数而子类未指定构造函数,那么将调用父类的哪一个构造函数呢?

对于继承中涉及到的构造函数的调用问题是比较复杂的。

首先,父类的构造函数先于子类的构造函数被调用。

其次,如果父类和子类都没有自定义构造函数,那么调用的都是默认的构造函数。

    public class Child : Father
    {
        public Child()
        {
            Console.WriteLine("Call Child()");
        }
    }

    public class Father
    {
        public Father()
        {
            Console.WriteLine("Call Father()");
        }

        public Father(string ss)
        {
            Console.WriteLine("Call Father(String)"+ss);
        }
   }

//实例化

Child c = new Child();

输出:

图片 25

若在Child 中指定调用父类的构造函数:

  public Child():base(“f”)

        {

            Console.WriteLine(“Call Child()”);

        }

输出:

图片 26

结论:从中可看出父类构造函数先于子类构造函数被调用,若子类构造函数不指定调用哪一个,一般会根据参数自动匹配。

    //多播委托
    //在委托实例上+=函数标识符即可在一个委托下注册多个函数
    //在一个委托上注册多个函数,执行委托时会按顺序执行每个函数。
    //返回值是最后一个执行的函数所返回的值,每个函数只能用相同的参数

  • 重写了object类的Equals方法和GetHashCode方法。
  • 因为是值对象类型,因此不继承Entity类。

3 类

View Code 

Address和Name两个类注意两点:

3.8 静态类和静态成员

1)静态类是密封的,因此不可被继承。

2)静态类不能包含实例构造函数,但仍可声明静态构造函数以分配初始值或设置某个静态状态。

3)静态方法和属性只能访问静态字段和静态事件。

4)静态成员在第一次被访问之前并且在任何静态构造函数(如调用的话)之前初始化。

5)静态构造函数有以下特点:

l 静态构造函数既没有访问修饰符,也没有参数。

l 在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数来初始化类。

l 无法直接调用静态构造函数。

l 在程序中,用户无法控制何时执行静态构造函数。

l 静态构造函数的典型用途是:当类使用日志文件时,将使用这种构造函数向日志文件中写入项。

l 静态构造函数在为非托管代码创建包装类时也很有用,此时该构造函数可以调用
LoadLibrary 方法。

//在泛型类外部,不能使用泛型形参,必须提供实参,实参就是真实的类型   

图片 27

6 异常处理

默认参数

 1 using System.Collections.Generic;
 2 
 3 namespace Demo.Fluent.Entities.Domain
 4 {
 5     public class Product : Entity<Product>
 6     {
 7         public virtual string ProductCode { get; set; }
 8 
 9         public virtual string ProductName { get; set; }
10 
11         public virtual string Description { get; set; }
12 
13         private readonly IList<Order> orders;
14 
15         public virtual IList<Order> Orders
16         {
17             get
18             {
19                 return orders;
20             }
21         }
22     }
23 }

2.类的访问权限遵从子类访问权限不能大于基类,要么其权限等于基,要么小于基。(类只有两种访问权限,要么internal要么public,所以很好记。基类如果是internal则子类不允许是public)

6)在Domain文件夹下,添加值对象类Address类、Name类,实体类:Customer类、Product类和Order类。

View
Code

8)添加用于测试的控制台应用程序Demo.Fluent.Console工程。

图片 28图片 29

1.如果使用了new操作符,则结构的构造函数会被调用,结构的每个成员都会被初始化。 

static void Test( string name , int age )
{

//一个表示智力的接口
public interface Iintelligence
{
    string Level { get; set; }
    string Language( );
}

图片 30图片 31

//以下实现一个包装制造好的产品和每包装好一个产品就记录进日志的逻辑
//使用了两个委托
 
//产品
public class Product
{
    public string ProductName { get; set; }
    public double ProductPrice { get; set; }
}

1.virtual操作符 

    //类的成员
    public static void GetTotalCount( )
    {
        ShowTotalCount( ); //类的成员GetTotalCount无法引用对象的ShowTotalCount成员
    }
}

假如没有泛型……

public class Cow : Animal
{
    public Cow( string name )
    {
        base.Name = name;
    }
}

public interface IListable { }

索引器重载 

3.声明常量时必须同时赋值,声明只读字段时可以不用立即赋值。

可以将params参数描述为可接收个数不固定的相同类型的参数,这种参数看起来好像跟直接传递数组差不多,实际上params参数和数组参数的唯一区别只有一个,那就是传参数时可以不传数组,每个参数用逗号隔开就可以了!每个参数都会被方法自动压入params参数数组中。也即传参时可以不传数组却可以被方法当成数组处理。params参数必须遵守以下规则

结构实现接口

图片 32图片 33

public class Myclass : BaseClass//此处表示该类将从指定的基类派生
{
    public Myclass( )
    {
        Console.WriteLine ( “派生类构造函数被执行” );
    }
}

委托弊端

}
//所有的枚举类型都可以被当做0,比如传递0给一个需要枚举作为参数的方法
static void Test ( MyEnum myEnum ) { }

public class Stack
{
    public Cell Push(Cell o)
    {
        //其它实现逻辑略……
        return o;
    }
}

引用类型(Reference
Type)

//根据我上面的定理:赋值时编译器会检查赋值操作符左右两边两个操作数其类型的等同性,不相等则会视为错误

Action <in T1> ( T1 args1 ),
Action<in T1, in T2> ( T1 args1 , T2
args2 ),
Action <in T1, in T2, in T3>  ( T1 args1, T2 args2 , T3
args3),
……

图片 34图片 35

分布类

View Code

初始化成员

 

View Code

1.Internal

新的初始化器

基类与派生类之间的转换

图片 36图片 37

    //索引器,返回类型和get、set的实现可以随意,index可以是任意类型
    public string this [ int index ]
    {
        get { return stringAry [ index ]; }
        set { stringAry [ index ] = value; }
    }
}
static void Main ( string [ ] args )
{
    StringContainner strcontainner = new StringContainner ( );
    strcontainner.StringAry = { “sam” , “leo” , “korn” };
    Console.WriteLine ( strcontainner [ 0 ] ); //return sam
}

内置委托

4.Protected

public class Person : Animal
{
    public override int GetCount( )
    {
        return 100;
    }
}

通知

public class Publishing
{
    //事件拥有者必须拥有规范事件的委托,因为委托是中间角色,外部订阅事件时需要订阅到委托上去。
    private OderEventHandler o;
    //定义事件
    public event OderEventHandler PublishingBook
    {
        add
        {
            o += value;                
        }
        remove
        {
            o -= value;
        }
    }
}

View
Code

图片 38图片 39

//新书信息
public class Book:EventArgs
{
    string bookName;
    double bookPrice;
}

输出结果为

Person p = new Person();
Publishing publishing = new Publishing();
//此人用他的Buy方法(事件处理器)订阅了新书发布事件
publishing.PublishingBook += p.Buy;

static void Main( string [ ] args ) 
{
    string [ ] f = { “番茄” , “荔枝”,”芒果” };
    Test ( “水果” , f );//传数组
    Test (“生物”, “鲨鱼” , “海豚” , “乌贼”  ); //非数组一样可以传给方法            
}

public class Animal
{
    static Animal( )
    {
        Console.WriteLine( “Animal的静态构造函数被执行” );
    }
    public Animal( )
    {
        Console.WriteLine( “Animal的实例构造函数被执行” );
    }
}

图片 40图片 41

public class Book : IListable { }   

索引器的索引键的类型不同或个数不同就被视为重载。

第一个文件中的MyClass类只定义了它的方法 

2.sealed

如果显示写出了无论是无参或有参版的构造函数,那么默认构造函数将会自动隐藏。

    }
}

public class Programe
{
    static void Main ( string [ ] args )
    {
        Book [ ] b = { };
        int count=b.GetMyCount ( );
    }
}

6.事件是委托的包装器,定义事件字段不是在定义委托字段!

public class Publishing
{
    public event OderEventHandler PublishingBook;
    //出版社名字
    public string publishingName = "译林出版社";

    //触发新书发布事件
    public void TrrigerBook()
    {
        Book book = new Book();
        book.bookName = "万有引力之虹";
        book.bookPrice = 20.8;
        PublishingBook.Invoke(this,book);
    }
}

我喜欢这样理解,类内部定的非static的成员其所有权归属于对象所有,static的成员其所有权归属类所有。所以在定义的类内部有类的成员和对象的成员之分。

泛型类型形参变量不能使用==和!=操作符用于测试等同性,可以考虑使用Equals()方法或Object.Equals()方法。参考:[C#

系统类和系统接口]()

泛型派生

泛型也是类类型,具有类类型的特点,所以可以派生,继承泛型的子类可以是泛型类或非泛型类。 

元组(Tuple)

关系数据库中每条记录(行)被称为元组,而每一列的数据被称为元数。在C#中,一组有关联的数据组合在一起就称为元组,每个操作数则称为元数。C#预定义了8个叫做Tuple的元组类。

Tuple<T1>
Tuple<T1 , T2>
Tuple<T1 , T2 , T3>
Tuple<T1 , T2 , T3 , T4>
Tuple<T1 , T2 , T3 , T4 , T5>
Tuple<T1 , T2 , T3 , T4 , T5 , T6>
Tuple<T1 , T2 , T3 , T4 , T5 , T6 , T7>
Tuple<T1 , T2 , T3 , T4 , T5 , T6 , T7 , T8>

它最多允许接收8个形参用以创建一个元组对象,元数是只读的,不可写,可按顺序从item1-item8访问元数。因为每一个T类型同时也可以是一个元组,所以Tuple中的每个元数也称为元组。

//假如需要创建带有7个元数的元组,new一个Tuple时将写满一长串的实参,非常壮观
Tuple<int , int , string , string , float , Person,Tuple<Person>> stuInfoLong = new Tuple<int , int , string , string , float , Person,Tuple<Person>>( 1 , 32 , “sam” , “student” , 1.75f , new Person( ) , new Tuple<Person>(new Person()));
//为了不每次都调用Tuple的构造函数创建元组,可利用var关键字和Tuple提供的Create工厂方法创建元组实例,这样就不用写满一大片的类型实参了
//一条带有员工信息的元组
var stuInfo = Tuple.Create( 1 , “sam” , “programer” , 1.75f , Tuple.Create( “favit:none” ) );
Console.WriteLine( stuInfo.Item2 ); //print sam
//Create的最后一个参数是一个元组,要访问则需要调用它的某一个元数
Console.WriteLine( stuInfo.Item5.Item1 ); //print favit:none

Tuple通常用于以下情况:

1.当某函数需要返回多个值时,不使用out、ref而以返回一个元组作为替代 

static Tuple<int , string , float , Tuple<string>> Test( )
{
    return Tuple.Create( 1 , “sam” , 1.75f , Tuple.Create( “favit:none” ) );
}

2.当函数需要接收一系列参数时,不需要params而以元组作为替代 

static void Test(Tuple<int , string , float , Tuple<string>> tuple )
{
    int ID = tuple.Item1;
    string name = tuple.Item2;

}
static void Main(string [ ] args)
{
    Test( Tuple.Create( 1 , “sam” , 1.75f , Tuple.Create( “favit:none” ) ) );
}

 值元组(ValueTuple)

值元组是C#7.0的新类,framework4.7已经内置,如果是4.7以下版本则可以通过NuGet下载。

图片 42

ValueTuple
ValueTuple<T1>
ValueTuple<T1 , T2>
ValueTuple<T1 , T2 , T3>
ValueTuple<T1 , T2 , T3 , T4>
ValueTuple<T1 , T2 , T3 , T4 , T5>
ValueTuple<T1 , T2 , T3 , T4 , T5 , T6>
ValueTuple<T1 , T2 , T3 , T4 , T5 , T6 , T7>
ValueTuple<T1 , T2 , T3 , T4 , T5 , T6 , T7 , TRest>

区别于Tuple是引用类型的泛型,ValueTuple是结构类型的泛型,而且值元组不但可读可写,它还有新的语法糖使代码得以简化。声明一个值元组类型,可以使用ValueTuple的Create工厂方法,但用语法糖将进一步简化代码量,看起来很是牛逼:

(int ID, string Name, float Height) new_newValueInfo = ( 1 , “sam”, 1.75f ); //语法糖,左边是声明的类型,中间是变量,右边是值元组实例

//函数的样子已经变得陌生……-_-||

static ( int ID , string Name , float Height ) Test ( )
{
    return ( 1, “sam”, 1.75f );
}

//以下三种方式都可以创建一个值元组,第三种方式可以直接使用变量名点出字段,一目了然。
var valueInfo = ValueTuple.Create( 1 ,”sam” , 1.75 , new Person ( ) , 1 ,1 ,1 , ValueTuple.Create ( 100 ) ); 
var newValueInfo = ( 1, “sam”, 1.75f , new Person ( ) ); //语法糖
(int ID, string Name, float Height) new_newValueInfo = ( 1 , “sam”, 1.75f ); //语法糖

Console.WriteLine( valueInfo.Item8.Item1 );//print 100
Console.WriteLine( newValueInfo.Item2);//print sam
Console.WriteLine( new_newValueInfo.Height );//print 1.75

泛型方法

当泛型类所定义的类型形参不满足方法的需求时,可以考虑将方法定义成泛型方法,具有自己独有的类型形参的方法称为泛型方法。既然泛型方法也是一种方法,方法具有的一切特征,泛型方法也都具备,是共通的。

public class Unknown<T>
{
    //非泛型方法
    public static void Take1 ( T x ) { }
    //泛型方法
    public static void Take2<X> ( X x , T t ) { }
}

public class Anonymous
{
    //泛型方法
    public static void Take2<X, T> ( X x , T t ) { }
}

泛型方法重载

确定泛型方法重载是根据类型参数的个数不同或方法参数的不同。

调用泛型方法

调用泛型方法时,如果该方法的类型形参出现在方法的参数列表中则可以不指定类型实参,因为编译器会自行识别调用方法时圆括号中的参数类型进而确定类型形参的实参类型,但假如类型形参没有出现在方法的参数列表中,此时必须显示地指定方法的类型实参。 

图片 43图片 44

public class Anonymous
{
    public static void Take<T> ( T x,T y)  {  }
    public static void PickUp<T> ( string z )  {  }
}

public class Programe
{
    static void Main ( string [ ] args )
    {
        Anonymous.Take ( “sam”,”korn”  ); //Take<T>的T形参出现在了圆括号的参数列表中,调用方法时可以不指定类型实参,编译器自动推断
        Anonymous.PickUp<int> ( “sam” ); //PickUp<T>的T形参没有出现在圆括号的参数列表中,调用方法时必须指定类型实参
    }
}

View Code

协变与逆变

C#仅允许泛型接口和泛型委托支持类型形参的可变性(协变、逆变),什么意思呢?先看一个泛型接口 

public interface IFication<T>
{
    void Test ( T t );
}

public class Animal : IFication<Animal>
{
    public string Breathe; //动物具有呼吸的特性
    public void Test ( Animal t )
    {
        Console.WriteLine ( t.Breathe );
    }
}

public class Person : Animal, IFication<Person>
{
    public string Logic; //人具有逻辑思维的特性
    public void Test ( Person t )
    {
        Console.WriteLine ( t.Logic );
    }
}

//现在要将 IFication<Person>转换为 IFication<Animal>
IFication<Person> p = new Person ( );
IFication<Animal> a = p;
//提示无法将IFication<Person>类型隐式转换为IFication<Animal>
//现在假设我们允许将这两种类型进行一次转换,会发生什么呢?
//Person实现了IFication<T>接口的成员方法Test,Test此时接收的参数t的类型是Person,打印的 t.Logic的t就是Person的实例。
//如果我们允许将t的类型转换为Animal
//那么 t.Logic的t就变成了Animal,可是Animal对象并不具有Logic的特性,这就会发生异常,这叫类型间的不可转换(不可变性)

public interface IFication<T>
{
    T Test ( );
}

public class Animal : IFication<Animal>
{
    public string Breathe; //动物具有呼吸的特性
    public Animal Test ( )
    {
        Console.WriteLine ( Breathe );
        return this;
    }
}

public class Person : Animal, IFication<Person>
{
    public string Logic; //人具有逻辑思维的特性
    Person IFication<Person>.Test ( )
    {
        Console.WriteLine ( Breathe );
        return this;
    }
}

//现在要将 IFication<Animal>转换为 IFication<Person>
IFication<Animal> a = new Animal ( );
IFication<Person> p = a;
//提示无法将IFication<Animal>类型隐式转换为IFication<Person>
//现在假设我们允许将这两种类型进行一次转换,会发生什么呢?
//Animal实现了IFication<T>接口的成员方法Test,Test此时不管在其代码块内部做了任何操作,最终它都必须返回一个T类型,此时这个T类型是Animal类型。
//如果我们允许将返回的Animal类型转换为Person类型会怎么样?return this返回的是Animal类型的对象,而接口的方法成员定义好的返回类型是T

//如果转换发生,则T会被替换为Person类型
//可是return的Animal对象不可能转换成Person对象,这就会发生异常,这叫类型间的不可转换(不可变性)

还有更复杂的使用场景,但此处不再啰嗦,我做个总结来说明支持逆变协变的公式即可

//——————-支持逆变的三种写法——————-
//基类转子类是逆变,要让一个接口支持T类型的逆变,那么有三种情况:
//1.接口方法将T用于方法的参数,不用于方法的返回类型
//2.接口的方法将另一个支持T协变的接口用于方法的参数,不用于方法的返回类型
//3.接口的方法将另一个支持T逆变的接口用于方法的返回类型,不用于方法的参数

//所以,以下三种写法都支持T类型的逆变:

//第一种:
public interface IFication<in T>
{
    void Test ( T t );
}

//第二种:
public interface IFication<in T>
{
    void Test ( IRecation<T> );
}

public interface IRecation<out X>
{
    X ReTest ( );
}

//第三种:
public interface IFication<in T>
{
    IRecation<T> Test ( );
}

public interface IRecation<in X>
{
    void ReTest ( X x );
}

IFication<Animal> a = new Animal ( );
IFication<Person> p = a;

//——————-支持协变的三种写法——————-
//子类转基类是协变,要让一个接口支持T类型的协变,那么有三种情况:
//1.接口方法将T用于方法的返回类型,不用于方法的参数
//2.接口的方法将另一个支持T协变的接口用于方法的返回类型,不用于方法的参数
//3.接口的方法将另一个支持T逆变的接口用于方法的参数,不用于方法的返回类型

//所以,以下三种写法都支持T类型的协变:

//第一种:
public interface IFication<out T>
{
    T Test ( );
}

//第二种:
public interface IFication<out T>
{
    IRecation<T> Test ( );
}

public interface IRecation<out X>
{
    X ReTest ( );
}

//第三种:
public interface IFication<out T>
{
    void Test ( IRecation<T> t );
}

public interface IRecation<in X>
{
    void ReTest ( X x );
}

IFication<Person> p = new Person ( );
IFication<Animal> a = p;

/*确定泛型接口应支持T的协变或逆变         

1.T用于方法的参数,不用于方法的返回类型:IXinterface<in T>
2.另一个支持T逆变的接口用于方法的返回类型,不用于方法的参数:IXinterface<in T>         
3.另一个支持T协变的接口用于方法的参数,不用于方法的返回类型:IXinterface<in T> 
 

1.T用于方法的返回类型,不用于方法的参数:IXinterface<out T>
2.另一个支持T协变的接口用于方法的返回类型,不用于方法的参数:IXinterface<out
T>         
3.另一个支持T逆变的接口用于方法的参数,不用于方法的返回类型:IXinterface<out T>*/

 图片 45图片 46

参考了这篇大神的文章:.NET
4.0中的泛型协变和逆变

泛型约束

泛型的类型参数可以指向任何类型,假如要处理T类型,但T类型是不确定的类型,这就没法调用其构造函数创建其实例了。所以C#提供了泛型约束,用以指明T类型可用的类型范围。可以为类型形参指定多个约束,约束总是放在泛型类型声明的结尾处,约束以where
T : 开头,每个约束以逗号隔开。可以指定非密封类的约束如下::

1.类类型约束:如Animal,必须出现在约束的最开始且只能有一个,不能同时指定class或struct
2.引用类型约束:class,必须出现在约束的最开始且只能有一个,不能同时指定Animal或struct
3.值类型约束:struct,必须出现在约束的最开始且只能有一个,不能同时指定Animal或class
4.无参构造器约束:new( )
[如果不指定构造器约束则编译器不允许这样的类型形参调用构造函数]

2.接口约束:如ICompareble,可指定多个

public class Stack<T, W, S, X, Y, H, P>
    where T : IComparable // T类型必须实现IComparable接口
    where S : struct // S类型必须是结构
    where X : IComparable, new()    // X类型必须实现IComparable接口且拥有无参构造函数
    where Y : Animal // Y类型必须是Animal类或派生自Animal
    where H : class // H类型必须是一个引用类型
    where P : T //P类型必须派生自T
{

}

//一个容易出错的例子
public class Template<T> { }

public class List<X, Y>
    where X : Template<Y>
{
    public X GetX ( )
    {
        return new Template<Y> ( ); //提示:无法将Template<Y>隐式转换为X
    }
}

// X 可能是一个Template<Y>的派生,假设用户传递的是子类,则X=子,而new Template<Y>是直接创建new Template<Y>的实例(父),所以这个转换是失败的

如果基类泛型的某个类型形参有一个约束,那么从其派生的泛型类在继承基类泛型时提供的类型参数也必须得到与基类相同的约束,在保证与基类一致的约束前提下,派生泛型可以继续为类型参数增加更多的约束。

如果泛型方法是一个虚方法,当该方法为某个类型形参指定约束后,派生类如果重写了此方法,则不能再次对类型形参提供约束,因为只能修改基类方法的实现,而不能修改为方法所下的定义,约束已经在基类方法中完成定义,没必要重复为方法定义两次约束。但new隐藏基类的泛型方法可以提供新的约束,因为该方法是派生类自己的成员而非基类,所以不受限制。

泛型的CIL表示 

public class Stack<T>
{
    T [ ] Items;
}

图片 47

Stack ‘
1表示该泛型的元数是1,泛型中的类型参数以!表示

泛型静态构造函数

与普通类型的静态构造函数的逻辑是一致的,但需要注意
Animal<string>和Animal<Person>不是相同的类型,所以Animal<string>的静态成员(类成员)与Animal<Person>的静态成员是相互独立的版本,当访问这两个不同类型的成员或实例化它们时,它们的静态构造函数自然会各自独立执行而不是只执行一次。

泛型的本地代码

定义泛型时定义的是个类型模板,它并不能用来执行并创建出对象。这就像把类进行了一次抽象,运行时先要将模板转化为真正可执行的泛型类型,接着才能实例化泛型。运行时会根据类型参数(值类型或引用类型)的不同而创建不同的本地代码。,本地代码才使真正可执行的、能创建出对象的代码。

图片 48图片 49

//现在有一个Stack<T>的泛型模板,当使用它时会提供类型实参
//在运行时会根据类型实参的不同创建不同的本地代码   
public class Stack<T>
{
    public T x;
    public T Test (T t )
    {
        return t;
    }
}

//如果是值类型,那么不同的值类型会创建不同的本地代码
//所谓本地代码就是如下所示的、真正可用于实例化泛型的类型代码
//比如使用Stack<T>时提供的类型实参是Stack<int>,则运行时生成的本地代码如下
public class Stack<int>
{
    public int x;
    public int Test ( int t )
    {
        return t;
    }
}

//比如使用Stack<T>时提供的类型实参是Stack<long>,则运行时生成的本地代码如下
public class Stack<long>
{
    public long x;
    public long Test ( long t )
    {
        return t;
    }
}

//比如使用Stack<T>时提供的类型实参是Stack<Animal>等所有的引用类型,则运行时生成的本地代码都是objec引用,t如下
//这样做的好处不言而喻,不需要为Stack<Animal>、Stack<Person>等类似的引用类型单独生成不同的本地代码,避免本地代码爆炸
//实例化泛型类型时,object会指向具体的泛型实例在内存堆上的地址
public class Stack<object>
{
    public object x;
    public object Test ( object t )
    {
        return t;
    }
}

View Code

使用泛型

现在假设我们需要写一个能提供排序算法的MySort类,它的GetSort方法可以对int类型的数组执行排序。我们假设后来又需要对double类型的数组进行排序,那么我们唯一的办法就是重载GetSort方法,但这样的话就有两个版本的GetSort,它们的算法逻辑却是相同的,重复编写相同的代码是很蠢的事情,此时就可以考虑使用泛型了。  

图片 50图片 51

namespace ConsoleApp1
{
    //将数组元素按从小到大排序
    public class MySort
    {
        public void SetSort ( int [ ] intAry )
        {
            int aryLength = intAry.Length;

            for ( int i = aryLength – 1 ; i > 0 ; i– )
            {
                for ( int z = aryLength – 1 ; z > 0 ; z– )
                {
                    bool result = intAry [ z ].CompareTo ( intAry [ z – 1 ] ) < 0;
                    if ( result )
                    {
                        int temp = intAry [ z – 1 ];
                        intAry [ z – 1 ] = intAry [ z ];
                        intAry [ z ] = temp;
                    }
                }
            }
        }
    }
    class Program
    {
        static void Main ( string [ ] args )
        {
            MySort m = new MySort ( );
            int [ ] a = { 2 , 900 , 1 , 200 , 1000 , 2000 , 500 , 20000 , 5 , 300 , 0 , };
            m.SetSort ( a );
            foreach ( var item in a )
            {
                Console.WriteLine ( item );
            }
        }
    }
}

非泛型的排序算法

图片 52图片 53

namespace ConsoleApp1
{
    public class MySort<T> where T : IComparable
    {
        public void SetSort ( T [ ] TAry )
        {
            int aryLength = TAry.Length;

            for ( int i = aryLength – 1 ; i > 0 ; i– )
            {
                for ( int z = aryLength – 1 ; z > 0 ; z– )
                {
                    bool result = TAry [ z ].CompareTo ( TAry [ z – 1 ] ) < 0;
                    if ( result )
                    {
                        T temp = TAry [ z – 1 ];
                        TAry [ z – 1 ] = TAry [ z ];
                        TAry [ z ] = temp;
                    }
                }
            }
        }
    }

    class Program
    {
        static void Main ( string [ ] args )
        {
            MySort<double> m = new MySort<double> ( );
            double [ ] a = { 2 , 900 , 1 , 200 , 1000 , 2000 , 500 , 20000 , 5 , 300 , 0 , };
            m.SetSort ( a );
            foreach ( var item in a )
            {
                Console.WriteLine ( item );
            }
        }
    }
}

泛型排序算法

 

特性(Attribute)

特性也是一种类型,但用法与普通的类型有区别,使用[AttributeName(……)]将特性置于程序集、类型、结构、枚举、接口、委托、事件、字段、属性、方法、方法参数、泛型类型参数、返回值、程序模块(为了便于描述,暂且统称这些东西为特性的目标)等等的声明之前,用于对特性目标进行描述,编译器可以读取特性并自动根据特性的内部实现对特性目标采取相应的动作。 

内置特性

1.Obsolete
( string message , bool IsError )

描述某个对象不再被使用,提醒编码人员的注意。IsError可选,默认false。如果IsError为true则编译器会禁止对该对象的运行并在错误列表中显示参数指定的message,如果IsError为false则只是发生警告,不会禁止对该对象的运行。

[Obsolete(“此类已被抛弃,请使用XAnimal类创建实例”,true)]
public class Animal //Animal是Obsolete的特性目标
{
    public string name=”蚂蚁”;
}

Animal a = new Animal ( );
Console.WriteLine(a.name );

 图片 54

2.AttributeUsage ( AttributeTargets
, AllowMultiple = bool , Inherited = bool )

用于定义在自定义的特性类上,

ValidOn:AttributeTargets枚举:用于指定特性只能运用在什么对象上,比如程序集、类型、构造函数、委托等等……

AllowMultiple=bool:指定自定义的特性是否可以在一个特性目标上被运用多次。

Inherited=bool:假如某父类和子类同时运用了某个特性,Inherited指定运用到子类上的特性是否可以持有对运用到父类上的特性的访问,如果Inherited=true则必须保证AllowMultiple=true,否则抛错。

AttributeTargets.Assembly; // 特性可以应用于程序集
AttributeTargets.All; // 特性可以应用于任何特性的目标
AttributeTargets.Class;
AttributeTargets.Constructor;
AttributeTargets.Delegate;
//略……

下面是Inherited的例子:

[AttributeUsage(AttributeTargets.All, AllowMultiple = true,Inherited=true)]
public class A : Attribute 
{
    public string Name;
    public A(string str) { this.Name = str; } 
}

[A(“xxx”)]
public class Animal { }
    
[A(“yyy”)]
public class Person:Animal { }

class Program
{
    static void Main(string[] args)
    {
        Type t = typeof(Person);
        var attri = t.GetCustomAttributes(true); //获取Person的特性集合
        Console.WriteLine((attri[0] as A).Name); //yyy
        Console.WriteLine((attri[1] as A).Name); //xxx
        //如果Inherited=false 则attri数组只有一个实例,只返回yyy,不会返回父类的A.Name
        //另,GetCustomAttributes方法的参数是一个额外的开关,如果传递false,则不会迭代到父类的A特性
    }
}

3.assebly、module、return

打开项目的properties可以看到一个AsseblyInfo.cs文件,此文件大量运用了内置的assembly特性用于描述程序集的信息,例如公司、产品名,版本号等。注意,assebly和module这两个特性必须出现在所有using引用之后。assebly应出现在所有命名空间之前,而module应出现在所有类的声明之前。最后,return特性应出现在方法之前。下面是return特性的例子:

public class A : Attribute { public A(string description) { } }

public class Animal
{
    [return: A(“如果没有提供参数,将返回默认值sam”)]
    public string Name()
    {
        return “sam”;
    }

    public string Name(string name)
    {
        return name;
    }
}

4.Flags

这是一个专门应用在枚举上的特性,表示这是一个位标志枚举,可以利用这个特性使一组枚举值可以存储在一个变量中。参看本页的枚举一节。

自定义特性

使自定义的特性类从Attribute派生就可以了。从Attribute派生的类都自动隐性的添加了Attribute后缀,但声明特性或运用特性时可以不显示添加Attribute后缀,只有在以内联的方式访问特性时才需要提供后缀,如:typeof(AnalysisAttribute)

public class Analysis : Attribute
{
    public string Description { get; set; }
    public Analysis ( string message )
    {
        Description = message;
    }
}

[Analysis ( “此类是密封的,不能继承” )]
public sealed class Philosopher
{

}

public class Programe
{
    static void Main ( string [ ] args )
    {
        Type type = typeof ( Philosopher ); //获取Philosopher的Type表示
        Attribute attri= type.GetCustomAttribute ( typeof ( Analysis ) ); //在Philosopher的Type表示上获取Analysis特性
        Analysis analysis = attri as Analysis;
        Console.WriteLine ( analysis.Description );
    }
}

特性可以以逗号分隔写在一行,但合并的写法不适用于返回值、程序集和模块。

public class A : Attribute { }
public class B : Attribute { public SwitchAlias(string country) { } }

public class Animal
{
    [A]
    [B(“chinese”)]
    public string Name { get; set; }
}

public class Animal
{
    [A, B(“chinese”)]
    public string Name { get; set; }
}

 

 

 

 

 

[C#

  • 学习总目录]()

View Code

//能匹配委托签名和返回类型的函数可以注册在委托上
static void Show ( double x , double y )
{
    Console.WriteLine ( x + y );
}

//流程:初始化Animal对象,分配内存,扫描它的成员字段name并初始化它,然后进入{}代码体,代码体存在一段为name字段赋新值的代码,执行后退出。

现在可以为不需要逻辑验证的字段设置自动属性,比如不需要一个字段来做属性的后备数据存储支持,这样,编译器隐式地为属性创建隐藏的字段,你只需要定义属性就可以了。

你可以在自定义类型中使用运算符重载。如果这样做,那么该类型的对象就可以使用标准的运算符来进行运算,运算逻辑由自己定义,运算符重载跟定义一个函数差不多。不过必须使用operator关键字且形态必须是static。格式为:

3.派生类继承除基类的构造函数和析构函数之外的所有成员(包括静态成员),但继承了基类的成员后,派生类并不一定能访问继承得来的成员,能不能访问取决于为那些成员定义的权限修饰符

定义类时,
先声明权限,再声明形态(可选), 如 

2.属性

图片 55图片 56

    public string Language( )
    {
        return “Earth language”;
    }
}

订阅事件

//实现接口快捷键:在:后,光标移入接口名Alt+Shift+F10

方法返回值

1.abstract 

该类可以在任何命名空间中被访问。
 

2.方法中可声明常量字段,方法中不可声明只读字段。

图片 57

发表评论

电子邮件地址不会被公开。 必填项已用*标注