C#进行CAD二次开发

最近被公司分配到了做CAD二次开发。也是初次接触这方面的东西,其实是有些无从下手的感觉。因为公司这边也没有人有时间带我,只能是自己看书,然后再写一些Demo,再结合实际的应用来一点点的学习。废话不多说,推荐一本我入门的书籍,就是李冠亿先生的《深居浅出AutoCAD二次开发》,写的非常不错,把这本书中的Demo自己手写一遍大概就能知道CAD二次开发到底是个什么东西了。

1. 开发环境配置

1.1. 开发环境

  • Win10 x64
  • CAD 2010
  • Visual Studio 2012

1.2. 环境配置

  1. 首先要安装CAD软件
  2. 用Visual Studio新建一个类库项目。
  3. 需要添加常用的引用,在CAD目录下,下面介绍三个主要的DLL文件
  • AcDbMgd.dll 当处理图形文件中存储对象时引用。
  • AcMgd.dll 当处理AutoCAD应用程序和用户接口时引用。
  • AcGui.dll 当处理自定义文件时引用。

2. 程序与调试

2.1. 写第一个程序

  1. 在刚刚创建的类库中添加一个类
1
2
3
4
5
6
7
8
9
10
public class FisrtCADTest
{
[CommandMethod("Hello")]
public void Hello
{
Document acDoc = Application.DocumentManager.MdiActiveDocument;
Editor ed = acDoc.Editor;
ed.WriteMessage("Hello World!");
}
}
  1. 编译这个程序
  2. 启动CAD在命令行中输入netload,然后在打开的窗口中找到刚编译的程序的dll,加载后在命令行输入Hello,猜猜会出现什么?没错,你的第一个CAD二次开发的程序已经完美运行了。

2.2. 如何调试程序

书中介绍了一种方法,不再赘述,下面来说一下我常用的方法
在解决方案资源管理器中右击解决方案,选择“添加”-“现有项目”
add_debug_cad_project
在打开的对话框中找到CAD安装路径下的acad.exe,选择打开
find_acad
将刚添加的项目设置为启动项目
set startup
设置项目属性
set property
修改调试器类型如下图所示
set debugger type
在需要的地方设置断点,启动项目,会发现CAD程序会被找开,netload加载程序集,就可以调试啦

3. 错误记录

1、CAD崩溃,错误“unhandled access violation reading”=“未将对象引用设置到对象的实例”。

2、建模操作错误:指向给定边的指针为空。
建模操作错误:访问冲突。
acdbmgdeGeneralModelingFailure

  • 情形一

这类错误是CAD直接在其命令行中输出提示的,有时候会引发CAD崩溃,但有时候又不会引发崩溃。

我碰到的这种类错误的情况是:频繁对一些Region进行重复炸碎,出现了这类错误。重复时间间隔越短越容易出问题,有时候中间间隔十几秒半分钟,但还会引发这类错误。

原因可能是炸碎之后对象的释放不及时,CAD内存管理上出了问题;炸碎之后的实体存放在了DBObjectCollection中,虽然这个类的继承了IDisopsable,并且实现了Dispose方法和析构函数,但C#对它的释放时机好像没有把握对,释放的太晚,可能我第一次炸碎之后的得到的对象,在我第二次进行炸碎操作的时候还没有释放掉,或者在炸碎的过程中进行了释放,于是出错误。

而对于释放不及时的原因,我猜想是因为DBObjectCollection内部有些地方用了GC.KeepAlive,而使得C#对它的释放不够及时。

解决的办法是尽量手动释放,发现这个问题之后,我刚开始是在使用完炸碎Region得到的DBObjectCollection之后,进行了统一的释放,但是还会出现这类错误。于是在使用DBObjectCollection的循环内部,每次循环结束后,主动调用每个DBObject的Dispose方法,然后在循环结束之后,再对DBObjectCollection进行统一释放,至此,没有再出现这类错误。

  • 情形二

注意foreach循环

3、不会命中断点,源代码与原始版本不同。

在时候用VS调试代码的时候设的断点无效,断点上会出来一个黄色的三角形,鼠标放上去会提示“不会命中断点,源代码与原始版本不同”,出现这种情况可以把项目中bin文件夹和obj文件夹清空,然后再重新生成解决方案。如果不能解决,可以找到工具->选项->调试->要求源文件与原始版本完成匹配,去掉勾选。

4、eNonCoplanarGeometry

当实体不共面时,即有些实体的Z值不为0,则进行想到计算、判断时(如面域的布尔运算),会有此提示,此时把对应实体或所有实体的Z值全部清零即可,或者是在进行计算、判断前先判断每个参与操作的实体的Z值是否为0,如果不为0则跳过不进行运算。

5、eNotApplicable

情况1:一个面域是复合面域,是一个回字形面域,但它里面的那一块非常非常小,此时将它炸开时会失败(不管是用接口的炸开还是用CAD命令炸开都会失败)。

4. CommandFlags

枚举值|描述
ActionMacro|可以用动作录制器录制命令动作;
DocReadLock|命令执行时将被只读锁定;
Interruptible|提示用户输入时可以中断命令;
Modal|别的命令运行时不能运行此命令;
NoActionRecording|不能用动作录制器录制命令动作;
NoBlockEditor|不能从块编辑器使用该命令;
NoHistory|不能将命令添加到repeat-last-command(重复上一个命令)历史列表;
NoPaperSpace|不能从图纸空间使用该命令;
NoTileMode|当TILEMODE置1时不能使用该命令;
NoUndoMarker|命令不支持撤销标记。用于不修改数据库因而也就无需出现在撤销记录中的那些命令;
Redraw|不清空取回的先选择后执行设置及对象捕捉设置;
Session|命令运行于应用程序上下文,而不是当前图形文档上下文;
Transparent|别的命令运行时可以运行此命令;
Undefined|只能通过全局名使用命令;
UsePickSet|清空取回的先选择后执行设置;

AutoMapper

DTO

什么是DTO

DTO(Data Transfer Object)就是数据传输对象。
表现层与应用层通过DTO进行交互,数据传输对象是没有行为的POCO对象,它的目的只是为了对领域对象进行数据的封装,实现层与层之间的数据传递。

为什么要用DTO

  • 领域对象更注重领域,而DTO更注重数据,是对领域模型的合理封装,从而不会将领域对象的行为过分暴露给表现层。

AutoMapper

源码地址.

Code First学习

1. CodeFirst学习笔记

1.1. EntityTypeConfiguration<T>

1.2. SetInitializer

1 CreateDatabaseIfNotExists<>

只有在没有数据库的时候才会根据数据库连接配置创建新的数据库。这种配置主要用于production环境,因为你不可能把你现在使用的数据库删除掉,那样会损失重要的数据。你需要让你的实施人员拿着与Fluent API配置对应的数据库脚本去更新数据库。

2 DropCreateDatabaseIfModelChanges<T>

只要Fluent API配置的数据库映射发生变化或者domain中的model发生变化了,就把以前的数据库删除掉,根据新的配置重新建立数据库。这种方式比较适合开发数据库,可以减少开发人员的工作量。

3 DropCreateDatabaseAlways<>
不管数据库映射或者model是否发生变化,每次都重新删除并根据配置重建数据库。这种方式可以适用于一些特殊情况的测试,比如说当每次测试结束之后把所有的测试数据都删除掉,并且在测试开始前插入一些基础数据。
可以用配置文件配置上述信息

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="DatabaseInitializerForType OrderSystemContext"
value="System.Data.Entity.DropCreateDatabaseIfModelChanges[[OrderSystemContext]], EntityFramework" />

</appSettings>
</configuration>

1.3. 添加初始数据

1.4. 使用Fluent API 配置属性

1.4.1. 配置Length

Length用来描述数组的长度,当前包括string和Byte数组。
默认约定:Code First对string或byte数组的默认长度约定是max。注意:Sql Server Compact中默认最大数组长度是4000。
重写约定:使用HasMaxLength(nn),参数为可空整数。

1
Property(t => t.Name).HasMaxLength(50);

另外关于Length的Fluent API还有下面2个:

  • IsFixedLength(),配置属性为固定长度。

  • IsMaxLength(),配置属性为数据库提供程序允许的最大长度。

1.4.2. 配置Data Type

Data Type表示将.NET类型映射到的数据库的数据类型。
默认约定:列的数据类型由使用的数据库提供程序决定。以SQL Server为例:String->nvarchar(max),Integer->int,Byte[]->varbinary(max),Boolean->bit。
重写约定:使用HasColumnType(“xxx”),下面列子的Photo是byte[]类型,配置映射到image数据类型:

1
Property(t => t.Photo).HasColumnType("image");

1.4.3. 配置允许为空和不允许为空

默认约定:主键属性不允许为空,引用类型(String,array)允许为空,值类型(所有的数字类型,Datetime,bool,char)不允许为空,可空的值类型Nullable允许为空。
重写约定:使用IsRequired()配置不允许为空,使用IsOptional()配置允许为空。下面配置Name属性为不为空:

1
Property(t => t.Name).IsRequired();

1.4.4. 配置属性到指定列

默认约定:映射到与属性名相同的列。

重写约定:使用Property(t=>t.属性名).HasColumnName(“xxx”)。下面配置Name映射到DepartmentName:

1
Property(t => t.Name).HasColumnName("DepartmentName");

1.4.5. 配置主键

默认约定:
(1)属性名为ID或Id的默认为主键
(2)类名+ID或类名+Id默认为主键 (其中ID或Id的优先级大于类名+ID或类名+Id)

重写约定:使用HasKey(t=>t.属性名)。下面将BlogId配置为主键:

1
HasKey(t => t.BlogId);

1.4.6. 配置组合主键

下面的例子将DepartmentId和Name属性组合作为Department类型的主键:

1
HasKey(t => new { t.DepartmentId, t.Name });

1.4.7. 配置Database-Generated

默认约定:整型键:Identity。

重写约定:使用Property(t => t.属性名).HasDatabaseGeneratedOption(DatabaseGeneratedOption)。

DatabaseGeneratedOption枚举包括三个成员:
(1) None:数据库不生成值
(2) Identity:当插入行时,数据库生成值
(3) Computed:当插入或更新行时,数据库生成值

整型默认是Identity,数据库生成值,自动增长,如果不想数据库自动生成值,使用DatabaseGeneratedOption.None。

Guid类型作为主键时,要显示配置为DatabaseGeneratedOption.Identity。

1.4.8. 配置TimeStamp/RowVersion的乐观并发

默认约定:这个没有默认约定。

配置:使用Property(t=>t.属性名).IsRowVersion()

1
Property(t => t.RowVersion).IsRowVersion();

1.4.9. 不配置TimeStamp的乐观并发

有些数据库不支持RowVersion类型,但是又想对数据库的一个或多个字段并发检查,这时可以使用Property(t=>t.属性名).IsConcurrencyToken(),下面的例子将SocialSecurityNumber配置为并发检查。

1
Property(t => t.SocialSecurityNumber).IsConcurrencyToken();

1.4.10. 配置String属性是否支持Unicode内容

默认约定:默认string是Unicode(在SQL Server中是nvarchar)的。

重写约定:下面的例子使用IsUnicode()方法将Name属性配置为varchar类型的。

1
Property(t => t.Name).IsUnicode(false);

1.4.11. 配置小数的精度和小数位数

默认约定:小数是(18,2)

配置:使用Property(t=>t.属性名).HasPrecision(n,n)

1
public decimal MilesFromNearestAirport { get; set; }

1.4.12. 复杂类型

默认复杂类型有以下规则:

(1) 复杂类型没有主键属性
(2) 复杂类型只能包含原始属性。
(3)在其他类中使用复杂类型时,必须表示为非集合类型。

使用DbModelBuilder.ComplexType方法显示配置为复杂类型:

1
modelBuilder.ComplexType<Address>();

1.4.13. 嵌套的复杂类型

嵌套的复杂类型只需显示配置外层,内层自动继承复杂类型的约定。

1.4.14. 配置复杂类型的属性

配置复杂类型的属性和配置实体属性一样,具体参考下面的实例。

1.4.15. 实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
  //实体
public class Trip
{
public Guid Identifier { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public decimal CostUSD { get; set; }
public string Description { get; set; }
public byte[] RowVersion { get; set; }
}

//复杂类型
public class Address
{
public int AddressId { get; set; }
public string StreetAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}

//复杂类型
public class PersonalInfo
{
public Measurement Weight { get; set; }
public Measurement Height { get; set; }
public string DietryRestrictions { get; set; }
}

//复杂类型
public class Measurement
{
public decimal Reading { get; set; }
public string Units { get; set; }
}

//实体
public class Person
{
public Person()
{
Address = new Address();
Info = new PersonalInfo()
{
Weight = new Measurement(),
Height = new Measurement()
};
}

public int PersonId { get; set; }
public int SocialSecurityNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
public byte[] Photo { get; set; }
public PersonalInfo Info { get; set; }
public byte[] RowVersion { get; set; }
}

//对实体Trip的配置,继承自EntityTypeConfiguration<T>
public class TripConfiguration : EntityTypeConfiguration<Trip>
{
public TripConfiguration()
{
//配置Identifier映射到TripId列,并设为主键,且默认值为newid()
HasKey(t => t.Identifier).Property(t => t.Identifier).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("TripId");
//配置CostUSD的精度为20,小数位数为3
Property(t => t.CostUSD).HasPrecision(20, 3);
//配置Description的长度为500
Property(t => t.Description).HasMaxLength(500);
//配置RowVersion乐观并发检查
Property(t => t.RowVersion).IsRowVersion();
}
}

//对实体Person的配置,继承自EntityTypeConfiguration<T>
public class PersonConfiguration : EntityTypeConfiguration<Person>
{
public PersonConfiguration()
{
//配置SocialSecurityNumber不允许为空且乐观并发检查
Property(t => t.SocialSecurityNumber).IsRequired().IsConcurrencyToken();
//配置FirstName不允许为空
Property(t => t.FirstName).IsRequired();
//配置LastName不允许为空
Property(t => t.LastName).IsRequired();
//配置Photo映射到数据库的数据类型为image
Property(t => t.Photo).HasColumnType("image");
//配置RowVersion乐观并发检查
Property(t => t.RowVersion).IsRowVersion();
}
}

//对复杂类型Address的配置,继承自ComplexTypeConfiguration<T>
public class AddressConfiguration : ComplexTypeConfiguration<Address>
{
public AddressConfiguration()
{
//配置AddressId映射到AddressId列
Property(t => t.AddressId).HasColumnName("AddressId");
//配置StreetAddress长度为100并映射到StreetAddrress列
Property(t => t.StreetAddress).HasMaxLength(100).HasColumnName("StreetAddress");
//配置State长度为50并映射到State列
Property(t => t.State).HasMaxLength(50).HasColumnName("State");
//配置City长度为50并映射到City列
Property(t => t.City).HasMaxLength(50).HasColumnName("City");
//配置ZipCode映射到ZipCode列,不支持Unicode内容,并设为固定长度为6
Property(t => t.ZipCode).IsUnicode(false).IsFixedLength().HasMaxLength(6).HasColumnName("ZipCode");
}
}

//对复杂类型PersonalInfo的配置,继承自ComplexTypeConfiguration<T>
public class PersonalInfoConfiguration : ComplexTypeConfiguration<PersonalInfo>
{
public PersonalInfoConfiguration()
{
//配置DietryRestrictions长度为100
Property(t => t.DietryRestrictions).HasMaxLength(100);
}
}

public class BreakAwayContext : DbContext
{
public DbSet<Trip> Trips { get; set; }
public DbSet<Person> People { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//注册配置
modelBuilder.Configurations.Add(new TripConfiguration());
modelBuilder.Configurations.Add(new PersonConfiguration());
modelBuilder.Configurations.Add(new AddressConfiguration());
modelBuilder.Configurations.Add(new PersonalInfoConfiguration());
base.OnModelCreating(modelBuilder);
}
}

异常记录

从 datetime2 数据类型到 datetime 数据类型的转换产生一个超出范围的值

触发该错误的条件如下:

  • SQL Server数据库版本中的字段类型为datetime2
  • 数据库中,某个要进行Add或者Edit的字段的数据类型为datetime,比如表A中的today字段,类型为datetime。而后台代码进行数据库操作时,并没有给today(datetime类型)赋值。结果就是VS2015编译的时候默认将其类型编译为datetime2,导致用EF进行add和edit操作的时候出现该异常。

解决方案:

  • 给这些字段一个值(不管它是否在数据库设置了默认值),并且日期要大于1753年1月1日,这是最简单的方法。
  • 将数据库类型修改为datetime?类型,也就是说允许为空。
  • 修改数据库字段类型为datetime2类型,前提是数据库要支持该类型。
  • 在C#中用new DateTime(year,month,day,hour,minute,second)来限制精度,原因之后会在datetime2和datetime的区别中提到。
  • 这个方法不太推荐,将model的edmx中的providerManifestToken设置成2005,这样ef就默认转化成datetime。

datetime2和datetime的区别:

官方MSDN对于datetime2的说明:定义结合了 24 小时制时间的日期。 可将 datetime2 视作现有 datetime 类型的扩展,其数据范围更大,默认的小数精度更高,并具有可选的用户定义的精度。

这里值的注意的是datetime2的日期范围是”0001-01-01 到 9999-12-31”(公元元年 1 月 1 日到公元 9999 年 12 月 31 日)。

而datetime的日期范围是:”1753 年 1 月 1 日到 9999 年 12 月 31 日“。这里的日期范围就是造成“从 datetime2 数据类型到 datetime 数据类型的转换产生一个超出范围的值”这个错误的原因。

1
2
3
DateTime字段类型对应的时间格式是yyyy-MM-dd HH:mm:ss.fff,3个f,精确到1毫秒(ms),示例2014-12-0317:06:15.433.
DateTime2字段类型对应的时间格式是yyyy-MM-dd HH:mm:ss.fffffff,7个f,精确到0.1微秒(μs),示例2014-12-0317:23:19.2880929.
如果用SQL的日期函数进行赋值,DateTime字段类型要用GETDATE(),DateTime2字段类型要用SYSDATETIME()。

通向财务自由

第1章 圣杯传奇

踏着英雄的足迹,在我们以为会发现邪恶的地方,找到了上帝。在以为杀害他人的地方,我们杀害了自己。在以为走出去的地方,我们将回到自己存在的中心。在以为一切都完了的地方,我们却与整个世界在一起。

常用命令行

查看端口占用指令

查看所有进程占用的端口

1
netstat  -ano

查看指定端口的程序

1
netstat -ano|findstr “端口号”

通过PID查看进程名称

1
tasklist | findstr "端口号"

UML类图的基本关系

在UML类图中,常见的有以下几种关系: 泛化(Generalization), 实现(Realization),关联(Association),聚合(Aggregation),组合(Composition),依赖(Dependency)

泛化(Generalization)

【泛化关系】:是一种继承关系,表示一般与特殊的关系,它指定了子类如何特化父类的所有特征和行为。例如:老虎是动物的一种,即有老虎的特性也有动物的共性。
【箭头指向】:带三角箭头的实线,箭头指向父类
Generalization

实现(Realization)

【实现关系】:是一种类与接口的关系,表示类是接口所有特征和行为的实现.
【箭头指向】:带三角箭头的虚线,箭头指向接口
Realization

关联(Association)

【关联关系】:是一种拥有的关系,它使一个类知道另一个类的属性和方法;如:老师与学生,丈夫与妻子关联可以是双向的,也可以是单向的。双向的关联可以有两个箭头或者没有箭头,单向的关联有一个箭头。
【代码体现】:成员变量
【箭头及指向】:带普通箭头的实心线,指向被拥有者
Association

上图中,老师与学生是双向关联,老师有多名学生,学生也可能有多名老师。但学生与某课程间的关系为单向关联,一名学生可能要上多门课程,课程是个抽象的东西他不拥有学生。
下图为自身关联:
AssociationSelf

聚合(Aggregation)

【聚合关系】:是整体与部分的关系,且部分可以离开整体而单独存在。如车和轮胎是整体和部分的关系,轮胎离开车仍然可以存在。
聚合关系是关联关系的一种,是强的关联关系;关联和聚合在语法上无法区分,必须考察具体的逻辑关系。
【代码体现】:成员变量
【箭头及指向】:带空心菱形的实心线,菱形指向整体
Aggregation

组合(Composition)

【组合关系】:是整体与部分的关系,但部分不能离开整体而单独存在。如公司和部门是整体和部分的关系,没有公司就不存在部门。
组合关系是关联关系的一种,是比聚合关系还要强的关系,它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期。
【代码体现】:成员变量
【箭头及指向】:带实心菱形的实线,菱形指向整体
Composition

依赖(Dependency)

【依赖关系】:是一种使用的关系,即一个类的实现需要另一个类的协助,所以要尽量不使用双向的互相依赖.
【代码表现】:局部变量、方法的参数或者对静态方法的调用
【箭头及指向】:带箭头的虚线,指向被使用者
Dependency

各种关系的强弱顺序:

泛化 = 实现 > 组合 > 聚合 > 关联 > 依赖
下面这张UML图,比较形象地展示了各种类图关系:
All

日常代码积累

1. 字符的全半角转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/// <summary>
/// 转全角(SBC case)
/// </summary>
/// <param name="input">任意字符串</param>
/// <returns>全角字符串</returns>
public static string ToSBC(this string input)
{
char[] c = input.ToCharArray();
for (int i = 0; i < c.Length; i++)
{
if (c[i] == 32)
{
c[i] = (char)12288;
continue;
}
if (c[i] < 127)
c[i] = (char)(c[i] + 65248);
}
return new string(c);
}
/// <summary>
/// 转半角(DBC case)
/// </summary>
/// <param name="input">任意字符串</param>
/// <returns>半角字符串</returns>
public static string ToDBC(this string input)
{
char[] c = input.ToCharArray();
for (int i = 0; i < c.Length; i++)
{
if (c[i] == 12288)
{
c[i] = (char)32;
continue;
}
if (c[i] > 65280 && c[i] < 65375)
c[i] = (char)(c[i] - 65248);
}
return new string(c);
}

2. javascript 大小写转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

//转换成大写人民币
var digitUppercase = function (n) {
var fraction = ['角', '分'];
var digit = [
'零', '壹', '贰', '叁', '肆',
'伍', '陆', '柒', '捌', '玖'
];
var unit = [
['元', '万', '亿'],
['', '拾', '佰', '仟']
];
var head = n < 0 ? '欠' : '';
n = Math.abs(n);
var s = '';
for (var i = 0; i < fraction.length; i++) {
s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, '');
}
s = s || '整';
n = Math.floor(n);
for (var i = 0; i < unit[0].length && n > 0; i++) {
var p = '';
for (var j = 0; j < unit[1].length && n > 0; j++) {
p = digit[n % 10] + unit[1][j] + p;
n = Math.floor(n / 10);
}
s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s;
}
return head + s.replace(/(零.)*零元/, '元')
.replace(/(零.)+/g, '零')
.replace(/^整$/, '零元整');
}

3. 获取两个List或数组的差集与交集

3.1. 差集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
list1.Add(3);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
list2.Add(5);
//得到的结果是4,5 即减去了相同的元素。
List<int> list3 = list2.Except(list1).ToList();
foreach (int i in list3)
{
MessageBox.Show(i.ToString());
}

3.2. 合并两个数组,并去掉重复元素,然后排序(C#)

1
2
3
4
5
6
7
List<int> numbers1 = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 12, 10 };
List<int> numbers2 = new List<int>() { 15, 14, 11, 13, 19, 18, 16, 17, 12, 10 };
var newQuerty = numbers1.Concat(
from n in numbers2
where !numbers1.Contains(n)
select n
).OrderBy(n=>n);

3.3. 合并两个数组,并去除合并后的重复数据, 并排序

1
2
3
4
int[] A={1,2,2,3,4,5,6,6,6};
int[] B={2,2,2,3,7,8,9,5};
List<int> list = new List<int>(A);
list.AddRange(B);

4. List<父类>引用List<子类>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Father
{

}
public class Son:Father
{
}

public class Test
{
public void Test()
{
List<Father> fathers = new List<Father>();
List<Son> sons = new List<Sons>();
sons = sons.Cast<Father>().ToList();
}
}

List<T> 的一些概念

  • List<T>初始值大小是4,自动扩容是以当前数组元素的两倍或InsertRange目标List的元素个数来扩容(如个大选如个)。如果比较确定的大小可以考虑提前设置,因为每次自动扩容需要重新分配数组和copy元素,性能损耗不小。
  • List<T>通过version来跟踪集合是否发生改变,如果在foreach遍历时发生改变则发生异常。
  • List<T>并非线程安全,任何使用的时候都要考虑当前环境是否可能有多线程存在,是否需要用锁来保证集合线程安全。

EasyUI Dialog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function DlogOwner(url) {
$("#dlg").dialog({
title: '人员信息',
modal: true,
closed: true,
href: url,
queryParams: { type: 'add', income_id: $("#income_id3").val() },
buttons: [{
text: '取消',
handler: function () {
$('#dlg').dialog('close');
}
}],
onClose: function () {
var income_id = $("#income_id3").val();
FillNewOwner(income_id);
},
//content: "<iframe scrolling='auto' frameborder='0' src='" + url + "' style='width:100%; height:100%; display:block;'></iframe>"
});
}
function getQueryParam(name) {
var obj = $('#dd').dialog('options');
var queryParams = obj["queryParams"];

return queryParams[name];
}

5. 隐藏下拉框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
select {  
/*Chrome和Firefox里面的边框是不一样的,所以复写了一下*/
border: solid 1px #000;

/*很关键:将默认的select选择框样式清除*/
appearance:none;
-moz-appearance:none;
-webkit-appearance:none;

/*在选择框的最右侧中间显示小箭头图片*/
background: url("http://ourjs.github.io/static/2015/arrow.png") no-repeat scroll right center transparent;


/*为下拉小箭头留出一点位置,避免被文字覆盖*/
padding-right: 14px;
}


/*清除ie的默认选择框样式清除,隐藏下拉箭头*/
select::-ms-expand { display: none; }

6. JavaScript日期转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Date.prototype.Format = function (fmt) { //author: meizz 
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));

return fmt;

}
var maydate = new Date();
console.log(" mydate :" + maydate.Format("yyyy-MM-dd hh:mm:ss"));

Visual Studio 2017无法添加引用

问题描述

本机装了Visual Studio2017与2012两个版本,不知道是不是有冲突还是其它的一些原因,在2017项目中添加引用时总是报错,错误信息如下图所示:
Error Image

解决方法

  1. 用管理员权限打开命令行(不用管理员权限进行下面的操作可能会提示无权限)
    在命行中打开如下路径:
    1
    C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\PublicAssemblies

此路径因安装visaul studio的位置不同而不同,我装在C盘

  1. 添加环境变量
    在path下添加
    1
    C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\x64

EditPath

  1. 执行如下命令
    1
    gacutil -i Microsoft.VisualStudio.Shell.Interop.11.0.dll

Add Succeed
完成后重启Visual Studio,查看问题是否解决

resharper

在线服务器
http://idea.iteblog.com/key.php
http://xidea.online

Log4net学习

Log4net的结构

Log4net有四种主要组件,分别是:

  • Logger:记录器
  • Repository:库
  • Appender:附着器
  • Layout:布局

    Logger

    Logger是应用程序需要交互的主要组件,它用来产生日志消息。产生的日志消息并不直接显示,还要预先经过Layout的格式化处理后才会输出。
    Logger提供了多种方式来记录一个日志消息,你可以在你的应用程序里创建多个Logger,每个实例化的Logger对象都被log4net框架作为命名实体(named entity)来维护。这意味着为了重用Logger对象,你不必将它在不同的类或对象间传递,只需要用它的名字为参数调用就可以了。log4net框架使用继承体系,继承体系类似于.NET中的名字空间。也就是说,如果有两个logger,分别被定义为a.b.c和a.b,那么我们说a.b是a.b.c的祖先。每一个logger都继承了祖先的属性。
    Log4net框架定义了一个ILog接口,所有的logger类都必须实现这个接口。
    Log4net框架定义了一个叫做LogManager的类,用来管理所有的logger对象。它有一个GetLogger()静态方法,用我们提供的名字参数来检索已经存在的Logger对象。如果框架里不存在该Logger对象,它也会为我们创建一个Logger对象。代码如下所示:
    1
    log4net.ILog log = log4net.LogManager.GetLogger("logger-name");

通常来说,我们会以类(class)的类型(type)为参数来调用GetLogger(),以便跟踪我们正在进行日志记录的类。传递的类(class)的类型(type)可以用typeof(Classname)方法来获得,或者可以用如下的反射方法来获得:

1
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType

尽管符号长了一些,但是后者可以用于一些场合,比如获取调用方法的类(class)的类型(type)。

日志的级别

日志的级别作为常量定义在log4net.spi.Level类中。
级别由低到高

1
All Debug Info Warn Error Fatal Off

在log4net框架里,通过设置配置文件,每个日志对象都被分配了一个日志优先级别。如果没有给一个日志对象显式地分配一个级别,那么该对象会试图从他的祖先继承一个级别值。
举例说明,当你创建了一个日志对象,并且把他的级别设置为INFO。于是框架会设置日志的每个Boolean属性。当你调用相应的日志方法时,框架会检查相应的Boolean属性,以决定该方法能不能执行。如下的代码:

1
2
3
Logger.Info("message");
Logger.Debug("message");
Logger.Warn("message");

对于第一种方法,Info()的级别等与日志的级别(INFO),因此日志请求会被传递,我们可以得到输出结果”message”。
对于第二种方法,Debug()的级别低于日志对象logger的日志级别(INFO),因此,日志请求被拒绝了,我们得不到任何输出。同样的,针对第三行语句,我们可以很容易得出结论。
你也可以显式地检查Logger对象的Boolean属性,如下所示:

1
2
3
4
if (logger.IsDebugEnabled)
{
Logger.Debug("message");
}

Repository

Repository主要用于负责日志对象组织结构的维护。在log4net的以前版本中,框架仅支持分等级的组织结构(hierarchical organization)。这种等级结构本质上是库的一个实现,并且定义在log4net.Repository.Hierarchy 名字空间中。要实现一个Repository,需要实现log4net.Repository.ILoggerRepository 接口。但是通常并不是直接实现该接口,而是以log4net.Repository.LoggerRepositorySkeleton为基类继承。体系库 (hierarchical repository )则由log4net.Repository.Hierarchy.Hierarchy类实现。
如果你是个log4net框架的使用者,而非扩展者,那么你几乎不会在你的代码里用到Repository的类。相反的,你需要用到LogManager类来自动管理库和日志对象

Appender

一个好的日志框架应该能够产生多目的地的输出。比如说输出到控制台或保存到一个日志文件。log4net 能够很好的满足这些要求。它使用一个叫做Appender的组件来定义输出介质。正如名字所示,这些组件把它们附加到Logger日志组件上并将输出传递到输出流中。你可以把多个Appender组件附加到一个日志对象上。 Log4net框架提供了几个Appender组件。关于log4net提供的Appender组件的完整列表可以在log4net框架的帮助手册中找到。有了这些现成的Appender组件,一般来说你没有必要再自己编写了。但是如果你愿意,可以从log4net.Appender.AppenderSkeleton类继承。

Appender Filters

一个Appender 对象缺省地将所有的日志事件传递到输出流。Appender的过滤器(Appender Filters) 可以按照不同的标准过滤日志事件。在log4net.Filter的名字空间下已经有几个预定义的过滤器。使用这些过滤器,你可以按照日志级别范围过滤日志事件,或者按照某个特殊的字符串进行过滤。你可以在API的帮助文件中发现更多关于过滤器的信息。

Layout

Layout 组件用于向用户显示最后经过格式化的输出信息。输出信息可以以多种格式显示,主要依赖于我们采用的Layout组件类型。可以是线性的或一个XML文件。Layout组件和一个Appender组件一起工作。API帮助手册中有关于不同Layout组件的列表。一个Appender对象,只能对应一个Layout对象。要实现你自己的Layout类,你需要从log4net.Layout.LayoutSkeleton类继承,它实现了ILayout接口。

在程序中使用

在开始对你的程序进行日志记录前,需要先启动log4net引擎。这意味着你需要先配置前面提到的三种组件。你可以用两种方法来设定配置:在单独的文件中设定配置或在代码中定义配置。
因为下面几种原因,推荐在一个单独的文件中定义配置:

  • 你不需要重新编译源代码就能改变配置;
  • 你可以在程序正运行的时候就改变配置。这一点在一些WEB程序和远程过程调用的程序中有时很重要;
    考虑到第一种方法的重要性,我们先看看怎样在文件中设定配置信息。

配置文件

配置信息可以放在如下几种形式文件的一种中。
在程序的配置文件里,如AssemblyName.config 或web.config.
在你自己的文件里。文件名可以是任何你想要的名字,如AppName.exe.xyz等.
log4net框架会在相对于AppDomain.CurrentDomain.BaseDirectory 属性定义的目录路径下查找配置文件。框架在配置文件里要查找的唯一标识是标签。一个完整的配置文件的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler, log4net-net-1.0"/>
</configSections>
<log4net>
<root>
<level value="WARN" />
<appender-ref ref="LogFileAppender" />
<appender-ref ref="ConsoleAppender" />
</root>
<logger name="testApp.Logging">
<level value="DEBUG"/>
</logger>
<appender name="LogFileAppender"
type="log4net.Appender.FileAppender" >
<param name="File" value="log-file.txt" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n"/>
<param name="Footer" value="[Footer]\r\n"/>
<param name="ConversionPattern"
value="%d [%t] %-5p %c [%x] - %m%n"
/>
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="DEBUG" />
<param name="LevelMax" value="WARN" />
</filter>
</appender>
<appender name="ConsoleAppender"
type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern"
value="%d [%t] %-5p %c [%x] - %m%n"
/>
</layout>
</appender>
</log4net>
</configuration>

你可以直接将上面的文本拷贝到任何程序中使用,但是最好还是能够理解配置文件是怎样构成的。 只有当你需要在应用程序配置文件中使用log4net配置时,才需要在标签中加入

配置节点入口。对于其他的单独文件,只有标签内的文本才是必需的,这些标签的顺序并不是固定的。下面我们依次讲解各个标签内文本的含义:

1
2
3
4
5
<root>
<level value="WARN" />
<appender-ref ref="LogFileAppender" />
<appender-ref ref="ConsoleAppender" />
</root>

在框架的体系里,所有的日志对象都是根日志(root logger)的后代。 因此如果一个日志对象没有在配置文件里显式定义,则框架使用根日志中定义的属性。在标签里,可以定义level级别值和Appender的列表。如果没有定义LEVEL的值,则缺省为DEBUG。可以通过标签定义日志对象使用的Appender对象。声明了在其他地方定义的Appender对象的一个引用。在一个logger对象中的设置会覆盖根日志的设置。而对Appender属性来说,子日志对象则会继承父日志对象的Appender列表。这种缺省的行为方式也可以通过显式地设定标签的additivity属性为false而改变。

1
2
<logger name="testApp.Logging" additivity="false">
</logger>

Additivity的值缺省是true.

1
2
3
<logger name="testApp.Logging">
<level value="DEBUG"/>
</logger>

元素预定义了一个具体日志对象的设置。然后通过调用LogManager.GetLogger(“testAPP.Logging”)函数,你可以检索具有该名字的日志。如果LogManager.GetLogger(…)打开的不是预定义的日志对象,则该日志对象会继承根日志对象的属性。知道了这一点,我们可以说,其实标签并不是必须的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<appender name="LogFileAppender"
type="log4net.Appender.FileAppender" >
<param name="File" value="log-file.txt" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n" />
<param name="Footer" value="[Footer]\r\n"/>
<param name="ConversionPattern"
value="%d [%t] %-5p %c - %m%n"
/>
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="DEBUG" />
<param name="LevelMax" value="WARN" />
</filter>
</appender>

标签或单个的标签里的Appender对象可以用标签定义。标签的基本形式如上面所示。它定义了appender的名字和类型。 另外比较重要的是标签内部的其他标签。不同的appender有不同的标签。在这里,为了使用FileAppender,你需要一个文件名作为参数。另外还需要一个在标签内部定义一个Layout对象。Layout对象定义在它自己的标签内。标签的type属性定义了Layout的类型(在本例里是PatternLayout),同时也确定了需要提供的参数值。Header和Footer标签提供了一个日志会话(logging session)开始和结束时输出的文字。有关每种appender的具体配置的例子,可以在log4net\doc\manual\example-config-appender.html中得到。

log4net.Layout.PatternLayout中的转换模式(ConversionPattern)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
%m(message):输出的日志消息,如ILog.Debug(…)输出的一条消息
%n(new line):换行
%d(datetime):输出当前语句运行的时刻
%r(run time):输出程序从运行到执行到当前语句时消耗的毫秒数
%t(thread id):当前语句所在的线程ID
%p(priority): 日志的当前优先级别,即DEBUG、INFO、WARN…等
%c(class):当前日志对象的名称,例如:
模式字符串为:%-10c -%m%n
代码为:
ILog log=LogManager.GetLogger(“Exam.Log”);
log.Debug(“Hello”);
则输出为下面的形式:
Exam.Log - Hello
%L:输出语句所在的行号
%F:输出语句所在的文件名
%-数字:表示该项的最小长度,如果不够,则用空格填充
例如,转换模式为%r [%t]%-5p %c - %m%n 的 PatternLayout 将生成类似于以下内容的输出:
176 [main] INFO org.foo.Bar - Located nearest gas station.

最后,让我们看看在Appender元素里的标签。它定义了应用到Appender对象的过滤器。本例中,我们使用了LevelRangeFilter过滤器,它可以只记录LevelMin和LevelMax参数指定的日志级别之间的日志事件。可以在一个Appender上定义多个过滤器(Filter),这些过滤器将会按照它们定义的顺序对日志事件进行过滤。其他过滤器的有关信息可以在log4net的SDK文档中找到。

使用配置文件

关联配置文件

当我们创建了上面的配置文件后,我们接下来需要把它和我们的应用联系起来。缺省的,每个独立的可执行程序集都会定义它自己的配置。log4net框架使用 log4net.Config.DOMConfiguratorAttribute在程序集的级别上定义配置文件。
例如:可以在项目的AssemblyInfo.cs文件里添加以下的语句

1
2
[assembly:log4net.Config.DOMConfigurator(ConfigFile="filename",
ConfigFileExtension="ext",Watch=true/false)]

  • ConfigFile:指出了我们的配置文件的路径及文件名,包括扩展名。
  • ConfigFileExtension:如果我们对被编译程序的程序集使用了不同的文件扩展名,那么我们需要定义这个属性,缺省的,程序集的配置文件扩展名为”config”。
  • Watch (Boolean属性): log4net框架用这个属性来确定是否需要在运行时监视文件的改变。如果这个属性为true,那么FileSystemWatcher将会被用来监视文件的改变,重命名,删除等事件。
    其中:ConfigFile和ConfigFileExtension属性不能同时使用,ConfigFile指出了配置文件的名字,例如,ConfigFile=”Config.txt”
    ConfigFileExtension则是指明了和可执行程序集同名的配置文件的扩展名,例如,应用程序的名称是”test.exe”,ConfigFileExtension=”txt”,则配置文件就应该是”test.exe.txt”;
    也可以不带参数应用DOMConfiguratio():
    1
    [assembly: log4net.Config.DOMConfigurator()]

也可以在程序代码中用DOMConfigurator类打开配置文件。类的构造函数需要一个FileInfo对象作参数,以指出要打开的配置文件名。 这个方法和前面在程序集里设置属性打开一个配置文件的效果是一样的。

1
2
log4net.Config.DOMConfigurator.Configure(
new FileInfo("TestLogger.Exe.Config"));

DOMConfigurator 类还有一个方法ConfigureAndWatch(..), 用来配置框架并检测文件的变化。
以上的步骤总结了和配置相关的各个方面,下面我们将分两步来使用logger对象。

创建或获取日志对象

日志对象会使用在配置文件里定义的属性。如果某个日志对象没有事先在配置文件里定义,那么框架会根据继承结构获取祖先节点的属性,最终的,会从根日志获取属性。如下所示:

1
Log4net.ILog log = Log4net.LogManager.GetLogger("MyLogger");

输出日志信息

可以使用ILog的几种方法输出日志信息。你也可以在调用某方法前先检查IsXXXEnabled布尔变量,再决定是否调用输出日志信息的函数,这样可以提高程序的性能。因为框架在调用如ILog.Debug(…)这样的函数时,也会先判断是否满足Level日志级别条件。

1
2
if (log.IsDebugEnabled) log.Debug("message");
if (log.IsInfoEnabled) log.Info("message);

在程序中配置log4net

除了前面讲的用一个配置文件来配置log4net以外,还可以在程序中用代码来配置log4net框架。如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 和PatternLayout一起使用FileAppender

log4net.Config.BasicConfigurator.Configure(

new log4net.Appender.FileAppender(

new log4net.Layout.PatternLayout("%d

[%t]%-5p %c [%x] - %m%n"),"testfile.log"));



// using a FileAppender with an XMLLayout

log4net.Config.BasicConfigurator.Configure(

new log4net.Appender.FileAppender(

new log4net.Layout.XMLLayout(),"testfile.xml"));



// using a ConsoleAppender with a PatternLayout

log4net.Config.BasicConfigurator.Configure(

new log4net.Appender.ConsoleAppender(

new log4net.Layout.PatternLayout("%d

[%t] %-5p %c - %m%n")));



// using a ConsoleAppender with a SimpleLayout

log4net.Config.BasicConfigurator.Configure(

new log4net.Appender.ConsoleAppender(new

log4net.Layout.SimpleLayout()));

尽管这里用代码配置log4net也很方便,但是你却不能分别配置每个日志对象。所有的这些配置都是被应用到根日志上的。

log4net.Config.BasicConfigurator 类使用静态方法Configure 设置一个Appender 对象。而Appender的构造函数又会相应的要求Layout对象。你也可以不带参数直接调用BasicConfigurator.Configure(),它会使用一个缺省的PatternLayout对象,在一个ConsoleAppender中输出信息。如下所示:

1
log4net.Config.BasicConfigurator.Configure();

在输出时会显示如下格式的信息:

1
2
0 [1688] DEBUG log1 A B C - Test
20 [1688] INFO log1 A B C - Test

当log4net框架被配置好以后,就可以如前所述使用日志功能了。

解决log4net独占日志文件的问题以及 log4net的各种输出配置(Appender)

由于log4net默认情况下会独占日志文件,该文件不能被File.Open。
可以通过增加配置:来使用最小锁定模型(minimal locking model),以允许多个进程可以写入同一个文件。

各种appender说明:
在log4net的配置中,appender是最重要的部分,一般来说,每一种appender都表示一种日志的输出介质,如日志文件、EvengLog、数据库、控制台、邮件、ASP.NET页面等。

本文对各种内置的appender的配置提供了示例,但却远称不上详尽。要想了解每一种appender的参数和选项的说明,请参看该appender的SDK文档。

以下示例都是.NET 2.0下进行的, log4net的版本为1.2.10。

AdoNetAppender
详情参考 log4net.Appender. AdoNetAppender SDK文档。

AdoNetAppender的相关配置内容取决于目标数据库的provider。下面仅提供SQL Server 2000的例子。

首先建立数据表:

CREATE TABLE [dbo].[Log]
(
[Id] [int] IDENTITY (1, 1) NOT NULL,
[Date] [datetime] NOT NULL,
[Thread] [varchar] (255) NOT NULL,
[Level] [varchar] (50) NOT NULL,
[Logger] [varchar] (255) NOT NULL,
[Message] [varchar] (4000) NOT NULL,
[Exception] [varchar] (2000) NULL
)

然后添加配置:

















































bufferSize表示批处理的日志事件,可以避免每次日志事件都访问数据库;ConnectionType指定了要使用的IDbConnection的完全限定类型名称;connectionString表示连接字符串;CommandText是SQL语句或存储过程;最后一组parameter节点描述了SQL语句或存储过程需要的参数。

AspNetTraceAppender

详情参考 log4net.Appender.AspNetTraceAppender SDK 文档。





这段配置可将日志信息输出到页面的Trace上下文环境。如果日志的级别低于WARN,会以System.Web.TraceContext.Write方法输出;如果级别为WARN或WARN以上则会以System.Web.TraceContext.Warn方法输出,下图中的日志信息的不同颜色可以说明这一点。效果图如下:

这在进行页面调试的时候可是很方便的。
BufferingForwardingAppender

详情参考 log4net.Appender.BufferingForwardingAppender SDK 文档。









BufferingForwardingAppender的主要作用是将输出到指定类型(这里是LogFileAppender)的Appender的日志信息进行缓存。bufferSize属性指定了缓存的数量,如果value为5,那么将在信息量达到6条的时候,把这些日志批量输出。appender-ref属性指定了缓存的Appender类型,同root节点一样,这里可以指定多个。

ColoredConsoleAppender
详情参考log4net.Appender.ColoredConsoleAppender SDK 文档。

ColoredConsoleAppender将日志信息输出到控制台。默认情况下,日志信息被发送到控制台标准输出流。下面这个示例演示了如何高亮显示Error信息。










效果如下:

还可以为不同的级别指定不同的颜色:














效果如下:

ConsoleAppender
详情参考 log4net.Appender.ConsoleAppender SDK 文档。

ConsoleAppender将日志信息输出到控制台标准输出流。





EventLogAppender
详情参考 log4net.Appender.EventLogAppender SDK 文档。

EventLogAppender将日志写入本地机器的应用程序事件日志中。默认情况下,该日志的源(Source)是AppDomain.FriendlyName,也可以手动指定其它名称。






FileAppender

详情参考 log4net.Appender.File Appender SDK 文档。

FileAppender将日志信息输出到指定的日志文件。









File指定了文件名称,可以使用相对路径,此时日志文件的位置取决于项目的类型(如控制台、Windows Forms、ASP.NET等);也可以使用绝对路径;甚至可以使用环境变量,如
AppendToFile指定是追加到还是覆盖掉已有的日志文件。
还可以添加如下属性来使用最小锁定模型(minimal locking model),以允许多个进程可以写入同一个文件。
ForwardingAppender

详情参考 log4net.Appender.ForwardingAppender SDK 文档。

ForwardingAppender可以用来为一个Appender指定一组约束。看下面这个示例:




在这个示例中,为ConsoleAppender添加了约束,Threshold为WARN。这意味着对于一条日志信息,如果直接使用ConsoleAppender,那么不论它是什么级别,总会进行输出,而如果使用这个ForwardingAppender,则只有那些WARN或WARN以上的日志才会发送到ConsoleAppender。
MemoryAppender

详情参考 log4net.Appender.MemoryAppender SDK 文档。

似乎不应该使用配置文件来配置MemoryAppender,但如果你非要这么做,看看这个示例(未验证):



NetSendAppender
详情参考 log4net.Appender.NetSendAppender SDK 文档。

NetSendAppender向特定用户的屏幕发送消息(未验证)。








OutputDebugStringAppender

详情参考 log4net.Appender.OutputDebugStringAppender SDK 文档。
下面这个例子描述了如何配置该Appender以向OutputDebugString API写入日志(未验证)。





RemotingAppender
详情参考 log4net.Appender.RemotingAppender SDK 文档。

RemotingAppender向特定的Sink发送日志信息(未验证):







RollingFileAppender

详情参考 log4net.Appender.RollingFileAppender SDK 文档。

RollingFileAppender以FileAppender为基础,与后者有着相同的配置选项。

下面这个例子演示了如何配置RollingFileAppender以写入log.txt文件。写入的文件名总是为log.txt(StaticLogFileName参数指定为true);根据文件大小(RollingStyle)来生成新的文件;最多保存有10个文件(MaxSizeRollBackups属性,而且一旦写满10个文件,就不再写入日志了),每个文件最大为10KB。这些文件名称为log.txt.1, log.txt.2…等。











SmtpAppender

详情参考 log4net.Appender.SmtpAppender SDK 文档。
SmtpAppender通过Smtp邮件服务器发送日志信息:

<appender name="SmtpAppender" type="log4net.Appender.SmtpAppender">
    <authentication value="Basic" />
    <to value="anderscui@tom.com" />
    <from value="anderscui@163.com" />
    <username value="anderscui" />
    <password value="password" />
    <subject value="test logging message" />
    <smtpHost value="smtp.163.com" />
    <bufferSize value="512" />
    <lossy value="true" />
    <evaluator type="log4net.Core.LevelEvaluator">
        <threshold value="WARN"/>
    </evaluator>
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%newline%date [%thread] %-5level %logger [%property{NDC}] - %message%newline%newline%newline" />
    </layout>
</appender>

将其中的to、from、username、password、subject、smtpHost配置正确才可能发送成功。bufferSize可将多条信息打包在一个邮件中。evaluator可以对日志进行过滤。
SmtpPickupDirAppender

详情参考 log4net.Appender.SmtpPickupDirAppender SDK 文档。

配置与SmtpAppender类似,但要把SmtpHost换为PickupDir(未验证)。















TraceAppender

详情参考 log4net.Appender.TraceAppender SDK 文档。

TraceAppender将日志信息写入System.Diagnostics.Trace系统(出现在输出窗口)。





UdpAppender
详情参考 log4net.Appender.UdpAppender SDK 文档。

下例演示了如何配置UdpAppender(未验证):








上面有若干个Appender标注为”未验证”的,是指这些Appender极少用到,或者在我的机器上没能实现 :(

希望这些内容能对您有所帮助。