https://blog.csdn.net/menglanyingfei/article/details/74992558html
public class HelloWorldjava
{ios
public void SayHello(string name)web
{正则表达式
}spring
}数据库
Pascal规则是指名称中单词的首字母大写 ,如EmployeeSalary、 ConfimationDialog、PlainTextEncoding。编程
public class Product设计模式
{api
private string productId;
private string productName;
public void AddProduct(string productId,string productName)
{
}
}
Camel规则相似于Pascal规则 ,但名称中第一个单词的首字母不大写 ,如employeeSalary、 confimationDialog、plainTextEncoding。
// Correct
public static const string ShippingType = "DropShip";
// Avoid
public static const string SHIPPINGTYPE = "DropShip";
public interface IConvertible
{
byte ToByte();
}
public class TableAttribute:Attribute
{
}
public class NullEmptyException:Exception
{
}
public class Employee
{
}
public class BusinessLocation
{
}
public class DocumentCollection
{
}
public class File
{
public void CreateFile(string filePath)
{
}
public void GetPath(string path)
{
}
}
局部变量的名称要有意义。不要直接用i, j, k, l, m, n, x, y, z等作变量名,但for循环除外。
全部的成员变量声明在类的顶端,用一个换行把它和方法分开。同时可使用成对的#region...#endregion标记,方便折叠。
布尔型变量或者方法通常能够用is、can或者has作前缀。如,isFinished, canWork等。
通常C#的编码风格要求花括号{另起一行,不要直接跟在类名和方法后面。
public Sample()
{
// TODO: 在此处添加构造函数逻辑
}
能够用缩写做为UI元素的前缀。常见UI组件的通常缩写形式:
Label --> lbl、Text --> txt、Button --> btn
Image --> img、 Widget --> Wgt、 List --> lst、CheckBox --> chk
Hyperlink --> lnk、Panel --> pnl、Table --> tab
ImageButton --> imb
判断条件是一个布尔变量时不要使用==进行条件判断。
// 不友好的写法
private bool isFinished = true;
if(isFinished == true)
{
// ...
}
// 正确的写法
private bool isFinished = true;
if(isFinished)
{
// ...
}
变量名是一个单词的尽可能不要缩写,多单词组成的变量名可适当缩写。
在类的顶部声明全部的成员变量,静态变量声明在最前面。
// Correct
public class Account
{
public static string BankName;
public static decimal Reserves;
public string Number {get; set;}
public DateTime DateOpened {get; set;}
public DateTime DateClosed {get; set;}
public decimal Balance {get; set;}
// Constructor
public Account()
{
// ...
}
}
若是一个方法超过25行,就须要考虑是否能够重构和拆分红多个方法。方法命名要见名知意,好的方法名能够省略多余的注释。方法功能尽可能单一。
只能继承一个基类,但能够继承多个接口。
接口不能实例化,但能够申明变量使用。
|
相同点 |
方法 |
是否能够继承多个 |
其余 |
抽象类 |
都不能够被实例化,但能够声明变量; abstract class A{}
class B:A{}
A aA = new B(); |
方法的实现体无关紧要;子类可覆盖抽象父类的方法;方法的访问限制有public,abstract,virtual,internal,protected internal; |
子类只可继承一个父类或抽象类,但能够继承多个接口; |
均可以有; |
接口 |
方法没有实现体,子类必须实现; 方法访问限制只有隐式的public |
|
不能够有成员变量(能够有属性),构造和析构函数;静态方法和常量; 不能够有static, virtual, abstract, or sealed. 能够有new,用来隐藏父接口中的实现; 接口能够做为类的成员,但不能够做为接口的成员; 注意以上指接口类自己的限制,但继承该接口的普通类,能够将继承的接口声明为virtual,abstract,但不能为const和static; |
|
含义 |
通用访问限制() |
特殊访问限制 |
fields |
成员变量,记录了类对象的状态信息; |
public:公用的,类的内外都可访问 private:私有的,类内访问; internal:项目内的,项目类访问 protected:本类或子类访问; protected internal:项目内的本类或子类访问; static: 归类全部,而非实例全部,因此实例不能调用; |
readonly:构造函数或定义是初始化;其余地方不能赋值; |
methods |
方法 |
virtual:子类能够重定义,亦可直接采用该函数;覆盖父方法,子类能够增长override; abstract:该关键字只能在抽象类中使用,没有函数实现体;本方法必须在子类中重写;相似纯虚函数; override:在子类中使用,表示该函数从父类继承,在本子类重写,覆盖父类方法;Base B = new Inherited ();B调用方法时,是调用子类重写过的。 new:子类隐藏父类方法,但,Base B = new Inherited ();B调用方法时,是调用父类本身的方法。 external: 定义在外部; sealed override: 本子类重写父类的方法,该方法在继续继承的子类里,不能从新定义,即不能重写; |
|
properties |
特性(区别于attribute),特殊的成员变量,有get和set,通常是对类的成员变量的存取封装,直接使用; |
对get或set有访问限制,好比 protected set{};表示只能在本类或子类中给属性赋值; |
访问时能够加this,也能够不加;加this可读性强,表示操做的是类实例的成员变量,而不是一个局部变量;
public int Var
{
get { return this.nVar; }
set { this.nVar = value; }
}
变量定义中含有一个问号,意思是这个数据类型是NullAble类型的。
变量定义中含有两个问号,意思是取所赋值??左边的,若是左边为null,取所赋值??右边的。
慕宗悫之长风
value属性是给form标签的input元素使用。该值设置控件的返回的值。该值不一样于控件的状态。好比说,一个checkbox,checked=”checked”,仅仅表示标签初始为选中状态。其携带的val值跟该状态没有关系,其实做者本身设置使用的,认为规定的一个值。咱们能够选择的时候,设置一个值,未选中的时候设置另外一个值。
https://blog.csdn.net/cai_huan_123/article/details/48269641
2015年09月07日 18:01:53
阅读数:316
托管资源指的是.NET能够自动进行回收的资源,主要是指托管堆上分配的内存资源。托管资源的回收工做是不须要人工干预的,有.NET运行库在合适调用垃圾回收器进行回收。
非托管资源指的是.NET不知道如何回收的资源,最多见的一类非托管资源是包装操做系统资源的对象,例如文件,窗口,网络链接,数据库链接,画刷,图标等。这类资源,垃圾回收器在清理的时候会调用Object.Finalize()方法。默认状况下,方法是空的,对于非托管对象,须要在此方法中编写回收非托管资源的代码,以便垃圾回收器正确回收资源。
在.NET中,Object.Finalize()方法是没法重载的,编译器是根据类的析构函数来自动生成Object.Finalize()方法的,因此对于包含非托管资源的类,能够将释放非托管资源的代码放在析构函数。
注意,不能在析构函数中释放托管资源,由于析构函数是有垃圾回收器调用的,可能在析构函数调用以前,类包含的托管资源已经被回收了,从而致使没法预知的结果。
原本若是按照上面作法,非托管资源也可以由垃圾回收器进行回收,可是非托管资源通常是有限的,比较宝贵的,而垃圾回收器是由CRL自动调用的,这样就没法保证及时的释放掉非托管资源,所以定义了一个Dispose()方法,让使用者可以手动的释放非托管资源。Dispose()方法释放类的托管资源和非托管资源,使用者手动调用此方法后,垃圾回收器不会对此类实例再次进行回收。Dispose()方法是由使用者调用的,在调用时,类的托管资源和非托管资源确定都未被回收,因此能够同时回收两种资源。
Microsoft为非托管资源的回收专门定义了一个接口:IDisposable,接口中只包含一个Dispose()方法。任何包含非托管资源的类,都应该继承此接口。
在一个包含非托管资源的类中,关于资源释放的标准作法是:
(1) 继承IDisposable接口;
(2) 实现Dispose()方法,在其中释放托管资源和非托管资源,并将对象自己从垃圾回收器中移除(垃圾回收器不在回收此资源);
(3) 实现类析构函数,在其中释放非托管资源。
在使用时,显示调用Dispose()方法,能够及时的释放资源,同时经过移除Finalize()方法的执行,提升了性能;若是没有显示调用Dispose()方法,垃圾回收器也能够经过析构函数来释放非托管资源,垃圾回收器自己就具备回收托管资源的功能,从而保证资源的正常释放,只不过由垃圾回收器回收会致使非托管资源的未及时释放的浪费。
在.NET中应该尽量的少用析构函数释放资源。在没有析构函数的对象在垃圾处理器一次处理中从内存删除,但有析构函数的对象,须要两次,第一次调用析构函数,第二次删除对象。并且在析构函数中包含大量的释放资源代码,会下降垃圾回收器的工做效率,影响性能。因此对于包含非托管资源的对象,最好及时的调用Dispose()方法来回收资源,而不是依赖垃圾回收器。
上面就是.NET中对包含非托管资源的类的资源释放机制,只要按照上面要求的步骤编写代码,类就属于资源安全的类。
下面用一个例子来总结一下.NET非托管资源回收机制:
|
Public classBaseResource:IDisposable { Private IntPtr handle;// 句柄,属于非托管资源 Private Componet comp;// 组件,托管资源 Private bool isDisposed =false;// 是否已释放资源的标志
Public BaseResource { }
//实现接口方法 //由类的使用者,在外部显示调用,释放类资源 Public void Dispose() { Dispose(true);// 释放托管和非托管资源
//将对象从垃圾回收器链表中移除, // 从而在垃圾回收器工做时,只释放托管资源,而不执行此对象的析构函数 GC.SuppressFinalize(this); }
//由垃圾回收器调用,释放非托管资源 ~BaseResource() { Dispose(false);// 释放非托管资源 }
//参数为true表示释放全部资源,只能由使用者调用 //参数为false表示释放非托管资源,只能由垃圾回收器自动调用 //若是子类有本身的非托管资源,能够重载这个函数,添加本身的非托管资源的释放 //可是要记住,重载此函数必须保证调用基类的版本,以保证基类的资源正常释放 Protected virtualvoid Dispose(booldisposing) { If(!this.disposed)// 若是资源未释放 这个判断主要用了防止对象被屡次释放 { If(disposing) { Comp.Dispose();// 释放托管资源 }
closeHandle(handle);// 释放非托管资源 handle= IntPtr.Zero; } this.disposed=true;// 标识此对象已释放 } } |
析构函数只能由垃圾回收器调用。
Despose()方法只能由类的使用者调用。
在C#中,凡是继承了IDisposable接口的类,均可以使用using语句,从而在超出做用域后,让系统自动调用Dispose()方法。 一个资源安全的类,都实现了IDisposable接口和析构函数。提供手动释放资源和系统自动释放资源的双保险
重载、覆盖和隐藏的区别:
1.重载发生在同一个类中,函数名相同;覆盖和隐藏发生在有继承关系的两个类中。
2.覆盖和隐藏的区别:
(1)若是派生类的函数与基类的函数同名,可是参数不一样。此时,不论有无virtual
关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)若是派生类的函数与基类的函数同名,而且参数也相同,可是基类函数没有virtual
关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
在实际应用中应避免出现隐藏。
应用区别:
覆盖:行为彻底取决于对象类型的
隐藏:行为取决于类型的指针
实例:
#include <iostream.h>
class Base
{
public:
virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
void g(float x){ cout << "Base::g(float) " << x << endl; }
void h(float x){ cout << "Base::h(float) " << x << endl; }
};
class Derived : public Base
{
public:
virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }
void g(int x){ cout << "Derived::g(int) " << x << endl; }
void h(float x){ cout << "Derived::h(float) " << x << endl; }
};
void main(void)
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
// Good : behavior depends solely on type of the object
pb->f(3.14f); // Derived::f(float) 3.14
pd->f(3.14f); // Derived::f(float) 3.14
// Bad : behavior depends on type of the pointer
pb->g(3.14f); // Base::g(float) 3.14
pd->g(3.14f); // Derived::g(int) 3 (surprise!)
// Bad : behavior depends on type of the pointer
pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
pd->h(3.14f); // Derived::h(float) 3.14
}
同C++相似,virtual是覆盖,new是隐藏,但new的优先级大于virtual。 即:若是子类方法加了new,不管父类有否virtual都是隐藏。
覆盖就是,在类的继承层次上,父类的实现被子类覆盖掉了。所以若是从子类引用的父类的函数的调用,则调用的是子类的覆盖后的实现。
综合:只有一种状况下是覆盖,父类加了virtual,子类增长了override的状况;
|
子类有new |
子类无new,无override |
子类无new,有override |
父类virtual |
隐藏 |
隐藏(有告警) |
覆盖 |
父类无virtual |
隐藏 |
隐藏 |
隐藏 |
public class MyBaseClass
{
public virtual void DoSomething()
{
Console.WriteLine("Base imp");
}
}
public class MyDerivedClass : MyBaseClass
{
public override void DoSomething()
{
Console.WriteLine("Derived imp");
}
}
class Program
{
static void Main(string[] args)
{
MyDerivedClass myObj = new MyDerivedClass();
MyBaseClass myBaseObj;
myBaseObj = myObj;
myBaseObj.DoSomething();
}
}
输出:Derived imp
其余状况输出:Base imp
//假设有这样一个TEST类:
class TEST
{
private:
int *num;
public:
TEST(int n)
{
num = new int;
*num = n;
}
void change(int anothernum)
{
*num = anothernum;
}
void print()
{
cout << *num << endl;
}
};
在上述TEST类中并无显式定义TEST类的复制构造函数, 那么其默认的复制构造函数应为:
TEST(const TEST & t)
{
num = t.num;
}
以上述TEST类为例, 假设有以下代码:
#include<iostream>using namespace std;
class TEST
{
private:
int *num;
public:
TEST(int n)
{
num = new int;
*num = n;
}
void change(int anothernum)
{
*num = anothernum;
}
void print()
{
cout << *num << endl;
}
};
int main(void)
{
TEST a(10);
TEST b(a);
a.print();
b.print();
b.change(100);
a.print();
b.print();
}
上述代码中并无显式定义复制构造函数, 运行结果以下:
10
10
100
100
//即更改了b对象的值, a对象的值也随之改变
上述结果能够证实, C++的默认构造函数为浅层复制, 也就是说a对象和b对象指向同一段内存空间.
在小伙伴的提示下, 上述代码存在一些问题:在TEST类中并无定义析构函数.析构函数的定义应该以下:
~TEST()
{
delete[] num;
}
那么定义析构函数和不定义析构函数都会引起什么样的问题呢?
综上所述, 在TEST类中, 无论是否认义析构函数都是不稳当的, 那这该怎么办呢?
依旧来看一段代码:
#include<iostream>using namespace std;
class TEST
{
private:
int *num;
public:
TEST(int n)
{
num = new int;
*num = n;
}
//此处显示定义了TEST类的复制构造函数
TEST(const TEST & t)
{
num = new int;
*num = *t.num;
}
void change(int anothernum)
{
*num = anothernum;
}
void print()
{
cout << *num << endl;
}
};
int main(void)
{
TEST a(10);
TEST b(a);
a.print();
b.print();
b.change(100);
a.print();
b.print();
}
上述代码中显式定义了复制构造函数, 自定义的复制构造函数中, 对a对象在堆上动态申请了空间, 而后再将b对象的值赋值给a对象新申请的这段内存空间.
运行结果以下:
10
10
10
100
//更改b对象的值并无改变a对象的值, 充分说明a对象和b对象所占的是不一样的两段内存空间
为何要先谈Java的深层拷贝呢?
由于在Java中, 只要是基本数据类型的数组, 使用上面介绍到的两个数组拷贝函数进行数组拷贝, 都是深层拷贝!
来看以下代码:
public class ArrayCopy {
public static void main(String args[]){
int[] array1 = {0, 1, 2, 3, 4, 5};
int[] array2 = Arrays.copyOf(array1, array1.length);
for(int num : array1){
System.out.printf("%3d", num);
}
System.out.println();
for(int num : array2) {
System.out.printf("%3d", num);
}
System.out.println();
array2[0] = 10;
for(int num : array1){
System.out.printf("%3d", num);
}
System.out.println();
for(int num : array2){
System.out.printf("%3d", num);
}
}
}
由于基本类型的数组的拷贝皆为深层拷贝额, 因此更改array2数组第一个元素的值, 并不会影响array1数组第一个元素的值. 运行结果以下:
0 1 2 3 4 5
0 1 2 3 4 5
0 1 2 3 4 5
10 1 2 3 4 5
不管是使用System.arraycopy()仍是Arrays.copyOf(), 只要用做类类型声明的数组时, 都执行浅层拷贝, 即源数组与拷贝数组指向同一段内存空间.
须要特别说明的是, 数组在Java中也是类的对象, 因此二维数组和三维数组在使用System.arraycopy()和Arrays.copyOf()的时候, 执行的也是浅层拷贝.
关于浅层拷贝就不在这里举例子了, 下面来看一看, 如何让类类型的数组执行深层拷贝.
看以下代码:
public class DeepCopy {
public static class cloths{
String color;
char size;
cloths(String col, char si){
color = col;
size = si;
}
}
public static void main(String[] args){
cloths[] c1 = {new cloths("red", 'l'), new cloths("blue", 'm')};
cloths[] c2 = new cloths[c1.length];
for(int i = 0; i < c1.length; i++){
cloths c = new cloths(c1[i].color, c1[i].size);
c2[i] = c;
}
c1[0].color = "yellow";
System.out.println(c2[0].color);
}
}
上述代码, 在复制每个类类型的数组元素时, 都给其new一段新的空间, 使之与源数组元素彻底隔离开. 因此运行结果以下:
red//源数组的第一个元素的color并无被改变
C#的浅层拷贝,采用的是System.Object的函数,MemberwiseClone()函数实现的。是一个值拷贝;该函数是一个protected函数,但很容易用一个public成员函数封装;
下面是一个浅层拷贝的例子:
public class Cloner
{
public int Val;
public Cloner(int newVal)
{
Val = newVal;
}
public object GetCopy()
{
return MemberwiseClone();
}
}
static void main(){
Cloner mySource = new Cloner(5);
Cloner myTarget = (Cloner)mySource.GetCopy();
Console.WriteLine("myTarget.MyContent.Val = {0}", myTarget.MyContent.Val);
mySource.MyContent.Val = 2;
Console.WriteLine("myTarget.MyContent.Val = {0}", myTarget.MyContent.Val);
}
所以浅层拷贝很适合类中的成员都是值类型;
对于值引用,若是仍然采用浅层拷贝方法,则会致使两个不一样的对性共享一个存储区,引致麻烦。
public class Content
{
public int Val;
}
public class Cloner
{
public Content MyContent = new Content();
public Cloner(int newVal)
{
MyContent.Val = newVal;
}
public object GetCopy()
{
return MemberwiseClone();
}
}
.NET提供了ICloneable来进行深层拷贝,
public class Content
{
public int Val;
}
public class Cloner : ICloneable
{
public Content MyContent = new Content();
public Cloner(int newVal)
{
MyContent.Val = newVal;
}
public object Clone()
{
Cloner clonedCloner = new Cloner(MyContent.Val);
return clonedCloner;
}
}
同时,深层拷贝也支持递归拷贝,以下:
public class Cloner : ICloneable
{
public Content MyContent = new Content();
...
public object Clone()
{
Cloner clonedCloner = new Cloner();
clonedCloner.MyContent = MyContent.Clone();
return clonedCloner;
}
}
struct是值引用,class是地址引用;
从值转换为引用=boxing
从引用转为值=unboxing
struct MyStruct
{
public int Val;
}
MyStruct valType1 = new MyStruct();
valType1.Val = 5;
object refType = valType1; //boxing
valType1.Val = 6;
MyStruct valType2 = (MyStruct)refType; //unboxing
Console.WriteLine("valType2.Val = {0}", valType2.Val);
输出:valType2.Val = 5
从中能够看出,boxing的过程并非把地址引用的过程,而是另外拷贝了一个新值。这样原来的值和boxing产生的新引用直接没有关系;
2009年11月01日 19:22:00
阅读数:627
在C#中有两个属性,分别为Property和Attribute,两个的中文意思都有特性、属性之间,可是用法上却不同,为了区别,本文暂把Property称为特性,把Attribute称为属性。
Attribute才是本文的主角,把它称为属性我以为很恰当。属性的意思就是附属于某种事物上的,用来讲明这个事物的各类特征的一种描述。而Attribute就是干这事的。它容许你将信息与你定义的C#类型相关联,做为类型的标注。这些信息是任意的,就是说,它不是由语言自己决定的,你能够随意创建和关联任何类型的任何信息。你能够做用属性定义设计时信息和运行时信息,甚至是运行时的行为特征。关键在于这些信息不只能够被用户取出来做为一种类型的标注,它更能够被编译器所识别,做为编译时的一种附属条件参加程序的编译。
如下部份内容及代码来源于《C#技术揭秘》(Inside C# Sencond Edition)
属性其实是一个派生自System.Attribute基类的类。System.Attribute类含有几个用于访问和检查自定义属性的方法。尽管你有权将任何类定义为属性,可是按照惯例来讲,从System.Attribute派生类是有意义的。
示例以下:
public enum RegHives
{
HKEY_CLASSES_ROOT,
HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE,
HKEY_USERS,
HKEY_CURRENT_CONFIG
}
public class RegKeyAttribute : Attribute
{
public RegKeyAttribute(RegHives Hive, String ValueName)
{
this.Hive = Hive;
this.ValueName = ValueName;
}
protected RegHives hive;
public RegHives Hive
{
get { return hive; }
set { hive = value; }
}
protected String valueName;
public String ValueName
{
get { return valueName; }
set { valueName = value; }
}
}
咱们在这里添加了不一样注册表的枚举、属性类的构造器以及两个特性(Property)。在定义属性时你能够作许许多多的事情,下面咱们看看如何在运行时查询属性。要想在运行时查询类型或成员所附着的属性,必须使用反射
假设你但愿定义一个属性,这个属性定义了将在其上建立对象的远程服务器。若是没有这个属性,就要把此信息保存在一个常量中或是一个应用程序的资源文件中。经过使用属性,只需用如下方法标注出类的远程服务器名便可:
using System;
namespace QueryAttribs
{
public enum RemoteServers
{
JEANVALJEAN,
JAVERT,
COSETTE
}
public class RemoteObjectAttribute : Attribute
{
public RemoteObjectAttribute(RemoteServers Server)
{
this.server = Server;
}
protected RemoteServers server;
public string Server
{
get
{
return RemoteServers.GetName(
typeof(RemoteServers), this.server);
}
}
}
[RemoteObject(RemoteServers.COSETTE)]
class MyRemotableClass
{
}
class Test
{
[STAThread]
static void Main(string[] args)
{
Type type = typeof(MyRemotableClass);
foreach (Attribute attr in
type.GetCustomAttributes(true))
{
RemoteObjectAttribute remoteAttr =
attr as RemoteObjectAttribute;
if (null != remoteAttr)
{
Console.WriteLine(
"Create this object on {0}.",
remoteAttr.Server);
}
}
Console.ReadLine();
}
}
}
运行结果为:
Creat this object on COSETTE。
注意:在这个例子中的属性类名具备Attribute后缀。可是,当咱们将此属性附着给类型或成员时却不包括Attribute后缀。这是C#语言的设计者提供的简单方式。当编译器看到一个属性被附着给一个类型或成员时,它会搜索具备指定属性名的System.Attribute派生类。若是编译器没有找到匹配的类,它就在指定的属性名后面加上Attribute,而后再进行搜索。所以,常见的使用作法是将属性类名定义为以Attribute结尾,在使用时忽略名称的这一部分。如下的代码都采用这种命名方式。
查询方法属性
在下面这个例子中,咱们使用属性将方法定义为可事务化的方法,只要存在TransactionableAttribute属性,代码就知道具备这个属性的方法能够属于一个事务。
using System;
using System.Reflection;
namespace MethodAttribs
{
public class TransactionableAttribute : Attribute
{
public TransactionableAttribute()
{
}
}
class SomeClass
{
[Transactionable]
public void Foo()
{}
public void Bar()
{}
[Transactionable]
public void Goo()
{}
}
class Test
{
[STAThread]
static void Main(string[] args)
{
Type type = Type.GetType("MethodAttribs.SomeClass");
foreach (MethodInfo method in type.GetMethods())
{
foreach (Attribute attr in
method.GetCustomAttributes(true))
{
if (attr is TransactionableAttribute)
{
Console.WriteLine(
"{0} is transactionable.",
method.Name);
}
}
}
Console.ReadLine();
}
}
}
运行结果以下:
Foo is transactionable.
Goo is transactionable.
查询字段属性:
假设有一个类含有一些字段,咱们但愿将它们的值保存进注册表。为此,可使用以枚举值和字符串为参数的构造器定义一个属性,这个枚举值表明正确的注册表hive,字符串表明注册表值名称。在运行时能够查询字段的注册表键。
using System;
using System.Reflection;
namespace FieldAttribs
{
public enum RegHives
{
HKEY_CLASSES_ROOT,
HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE,
HKEY_USERS,
HKEY_CURRENT_CONFIG
}
public class RegKeyAttribute : Attribute
{
public RegKeyAttribute(RegHives Hive, String ValueName)
{
this.Hive = Hive;
this.ValueName = ValueName;
}
protected RegHives hive;
public RegHives Hive
{
get { return hive; }
set { hive = value; }
}
protected String valueName;
public String ValueName
{
get { return valueName; }
set { valueName = value; }
}
}
class SomeClass
{
[RegKey(RegHives.HKEY_CURRENT_USER, "Foo")]
public int Foo;
public int Bar;
}
class Test
{
[STAThread]
static void Main(string[] args)
{
Type type = Type.GetType("FieldAttribs.SomeClass");
foreach (FieldInfo field in type.GetFields())
{
foreach (Attribute attr in
field.GetCustomAttributes(true))
{
RegKeyAttribute rka =
attr as RegKeyAttribute;
if (null != rka)
{
Console.WriteLine(
"{0} will be saved in"
+ " {1}////{2}",
field.Name,
rka.Hive,
rka.ValueName);
}
}
}
Console.ReadLine();
}
}
}
运行结果为:
Foo will be saved in HKEY_CURRENT_USER//Foo
你们能够看到,用属性来标注类、方法、字段,既能够把用户的自定义信息附属在实体上,又能够在运行时动态的查询。下面我将讲一些C#中默认的预约义属性,见下表:
预约义的属性 有效目标 说明
AttributeUsage Class 指定另外一个属性类的有效使用方式
CLSCompliant 所有 指出程序元素是否与CLS兼容
Conditional Method 指出若是没有定义相关联的字符串,编译器就能够忽略对这个方法的任何调用
DllImport Method 指定包含外部方法的实现的DLL位置
STAThread Method(Main) 指出程序的默认线程模型为STA
MTAThread Method(Main) 指出程序的默认模型为多线程(MTA)
Obsolete 除了Assembly、Module、Parameter和Return 将一个元素标示为不可用,通知用户此元素将被从将来的产品
ParamArray Parameter 容许单个参数被隐式地看成params(数组)参数对待
Serializable Class、Struct、enum、delegate 指定这种类型的全部公共和私有字段能够被串行化
NonSerialized Field 应用于被标示为可串行化的类的字段,指出这些字段将不可被串行化
StructLayout Class、struct 指定类或结构的数据布局的性质,好比Auto、Explicit或sequential
ThreadStatic Field(静态) 实现线程局部存储(TLS)。不能跨多个线程共享给定的静态字段,每一个线程拥有这个静态字段的副本
下面介绍几种经常使用的属性
1.[STAThread]和[MTAThread]属性
class Class1
{
[STAThread]
Static void Main( string[] args )
{
}
}
使用STAThread属性将程序的默认线程模型指定为单线程模型。注意,线程模型只影响使用COM interop的应用程序,将这个属性应用于不使用COM interop的程序将不会产生任何效果。
2. AttributeUsage属性
除了用于标注常规C#类型的自定义属性之外,还可使用AttributeUsage属性定义你使用这些属性的方式。文件记录的AttributeUsage属性调用用法以下:
[AttributeUsage( validon , AllowMutiple = allowmutiple , Inherited = inherited )]
Validon参数是AttributeTargets类型的,这个枚举值的定义以下:
public enum AttributeTargets
{
Assembly = 0x0001,
Module = 0x0002,
Class = 0x0004,
Struct = 0x0008,
Enum = 0x0010,
Constructor = 0x0020,
Method = 0x0040,
Property = 0x0080,
Field = 0x0100,
Event = 0x200,
Interface = 0x400,
Parameter = 0x800,
Delegate = 0x1000,
All = Assembly | Module | Class | Struct | Enum | Constructor| Method | Property| Filed| Event| Interface | Parameter | Deleagte ,
ClassMembers = | Class | Struct | Enum | Constructor | Method | Property | Field | Event | Delegate | Interface
}
AllowMultiple决定了能够在单个字段上使用某个属性多少次,在默认状况下,全部的属性都是单次使用的。示例以下:
[AttributeUsage( AttributeTargets.All , AllowMultiple = true )]
public class SomethingAttribute : Attribute
{
public SomethingAttribute( string str )
{
}
}
//若是AllowMultiple = false , 此处会报错
[Something(“abc”)]
[Something(“def”)]
class Myclass
{
}
Inherited参数是继承的标志,它指出属性是否能够被继承。默认是false。
Inherited AllowMultiple 结果
true false 派生的属性覆盖基属性
true false 派生的属性和基属性共存
代码示例:
using System;
using System.Reflection;
namespace AttribInheritance
{
[AttributeUsage(
AttributeTargets.All,
AllowMultiple=true,
// AllowMultiple=false,
Inherited=true
)]
public class SomethingAttribute : Attribute
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public SomethingAttribute(string str)
{
this.name = str;
}
}
[Something("abc")]
class MyClass
{
}
[Something("def")]
class Another : MyClass
{
}
class Test
{
[STAThread]
static void Main(string[] args)
{
Type type =
Type.GetType("AttribInheritance.Another");
foreach (Attribute attr in
type.GetCustomAttributes(true))
// type.GetCustomAttributes(false))
{
SomethingAttribute sa =
attr as SomethingAttribute;
if (null != sa)
{
Console.WriteLine(
"Custom Attribute: {0}",
sa.Name);
}
}
}
}
}
当AllowMultiple被设置为false时,结果为:
Custom Attribute : def
当AllowMultiple被设置为true时,结果为:
Custom Attribute : def
Custom Attribute : abc
注意,若是将false传递给GetCustomAttributes,它不会搜索继承树,因此你只能获得派生的类属性。
3.Conditional 属性
你能够将这个属性附着于方法,这样当编译器遇到对这个方法调用时,若是没有定义对应的字符串值,编译器就忽略这个调用。例如,如下方法是否被编译取决因而否认义了字符串“DEGUG”:
[Condition(“DEBUG”) ]
public void SomeDebugFunc()
{
Console.WriteLine(“SomeDebugFunc”);
}
using System;
using System.Diagnostics;
namespace CondAttrib
{
class Thing
{
private string name;
public Thing(string name)
{
this.name = name;
#if DEBUG
SomeDebugFunc();
#else
SomeFunc();
#endif
}
public void SomeFunc()
{ Console.WriteLine("SomeFunc"); }
[Conditional("DEBUG")]
[Conditional("ANDREW")]
public void SomeDebugFunc()
{ Console.WriteLine("SomeDebugFunc"); }
}
public class Class1
{
[STAThread]
static void Main(string[] args)
{
Thing t = new Thing("T1");
}
}
}
4. Obsolete 属性
随着代码不断的发展,你很能够会有一些方法不用。能够将它们都删除,可是有时给它们加上适当的标注比删除它们更合适,例如:
using System;
namespace ObsAttrib
{
class SomeClass
{
[Obsolete("Don't use OldFunc, use NewFunc instead", true)]
public void OldFunc( ) { Console.WriteLine("Oops"); }
public void NewFunc( ) { Console.WriteLine("Cool"); }
}
class Class1
{
[STAThread]
static void Main(string[] args)
{
SomeClass sc = new SomeClass();
sc.NewFunc();
// sc.OldFunc(); // compiler error
}
}
}
咱们将Obsolete属性的第二个参数设置为true,当调用时函数时编译器会产生一个错误。
E:/InsideC#/Code/Chap06/ObsAttrib/ObsAttrib/Class1.cs(20): 'ObsAttrib.SomeClass.OldFunc()' 已过期: 'Don't use OldFunc, use NewFunc instead'
5. DllImport和StructLayout属性
DllImport可让C#代码调用本机代码中的函数,C#代码经过平台调用(platform invoke)这个运行时功能调用它们。
若是你但愿运行时环境将结构从托管代码正确地编组现非托管代码(或相反),那么须要为结构的声明附加属性。为了使结构参数能够被正确的编组,必须使用StructLayout属性声明它们,指出数据应该严格地按照声明中列出的样子进行布局。若是不这么作,数据将不能正确地被编组,而应用程序可能会出错。
using System;
using System.Runtime.InteropServices; // for DllImport
namespace nativeDLL
{
public class Test
{
// [DllImport ("user32.dll")] // all the defaults are OK
[DllImport("user32", EntryPoint="MessageBoxA",
SetLastError=true,
CharSet=CharSet.Ansi, ExactSpelling=true,
CallingConvention=CallingConvention.StdCall)]
public static extern int MessageBoxA (
int h, string m, string c, int type);
[StructLayout(LayoutKind.Sequential)]
public class SystemTime {
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}
[DllImport ("kernel32.dll")]
public static extern void GetLocalTime(SystemTime st);
[STAThread]
public static void Main(string[] args)
{
MessageBoxA(0, "Hello World", "nativeDLL", 0);
SystemTime st = new SystemTime();
GetLocalTime(st);
string s = String.Format("date: {0}-{1}-{2}",
st.wMonth, st.wDay, st.wYear);
string t = String.Format("time: {0}:{1}:{2}",
st.wHour, st.wMinute, st.wSecond);
string u = s + ", " + t;
MessageBoxA(0, u, "Now", 0);
}
}
}
6. 配件属性
当使用.NET产生任何类型的C#工程时,会自动的产生一个AssemblyInfo.cs源代码文件以及应用程序源代码文件。AssemblyInfo.cs中含有配件中代码的信息。其中的一些信息纯粹是信息,而其它信息使运行时环境能够确保唯一的命名和版本号,以供重用你的配件的客户代码使用。
7. 上下文属性
.NET柜架还提供了另外一种属性:上下文属性。上下文属性提供了一种截取机制,能够在类的实例化和方法调用以前和以后进行处理。这种功能用于对象远程调用,它是从基于COM的系统所用的COM+组件服务和Microsoft Transaction Services(MTS)。
String.Format (String, Object) 将指定的 String 中的格式项替换为指定的 Object 实例的值的文本等效项。
String.Format (String, Object[]) 将指定 String 中的格式项替换为指定数组中相应 Object 实例的值的文本等效项。
String.Format (IFormatProvider, String, Object[]) 将指定 String 中的格式项替换为指定数组中相应 Object 实例的值的文本等效项。指定的参数提供区域性特定的格式设置信息。
String.Format (String, Object, Object) 将指定的 String 中的格式项替换为两个指定的 Object 实例的值的文本等效项。
String.Format (String, Object, Object, Object) 将指定的 String 中的格式项替换为三个指定的 Object 实例的值的文本等效项。
字符 |
说明 |
示例 |
输出 |
C |
货币 |
string.Format("{0:C3}", 2) |
$2.000 |
D |
十进制 |
string.Format("{0:D3}", 2) |
002 |
E |
科学计数法 |
1.20E+001 |
1.20E+001 |
G |
常规 |
string.Format("{0:G}", 2) |
2 |
N |
用分号隔开的数字 |
string.Format("{0:N}", 250000) |
250,000.00 |
X |
十六进制 |
string.Format("{0:X000}", 12) |
C |
|
|
string.Format("{0:000.000}", 12.2) |
012.200 |
经常使用的几种实例
复制代码 代码以下:
string str1 =string.Format("{0:N1}",56789); //result: 56,789.0
string str2 =string.Format("{0:N2}",56789); //result: 56,789.00
string str3 =string.Format("{0:N3}",56789); //result: 56,789.000
string str8 =string.Format("{0:F1}",56789); //result: 56789.0
string str9 =string.Format("{0:F2}",56789); //result: 56789.00
string str11 =(56789 / 100.0).ToString("#.##"); //result: 567.89
string str12 =(56789 / 100).ToString("#.##"); //result: 567
复制代码 代码以下:
string.Format("{0:C}",0.2)
结果为:¥0.20 (英文操做系统结果:$0.20)
默认格式化小数点后面保留两位小数,若是须要保留一位或者更多,能够指定位数
复制代码 代码以下:
string.Format("{0:C1}",23.15)
结果为:¥23.2 (截取会自动四舍五入)
格式化多个Object实例
复制代码 代码以下:
string.Format("市场价:{0:C},优惠价{1:C}",23.15,19.82)
复制代码 代码以下:
string.Format("{0:D3}",23) //结果为:023
string.Format("{0:D2}",1223) //结果为:1223,(精度说明符指示结果字符串中所需的最少数字个数。)
复制代码 代码以下:
string.Format("{0:N}", 14200) //结果为:14,200.00 (默认为小数点后面两位)
string.Format("{0:N3}", 14200.2458) //结果为:14,200.246 (自动四舍五入)
string.Format("{0:P}", 0.24583) //结果为:24.58% (默认保留百分的两位小数)
string.Format("{0:P1}", 0.24583) //结果为:24.6% (自动四舍五入)
复制代码 代码以下:
string.Format("{0:0000.00}", 12394.039) //结果为:12394.04
string.Format("{0:0000.00}", 194.039) //结果为:0194.04
string.Format("{0:###.##}", 12394.039) //结果为:12394.04
string.Format("{0:####.#}", 194.039) //结果为:194
下面的这段说明比较难理解,多测试一下实际的应用就能够明白了。
零占位符: 若是格式化的值在格式字符串中出现“0”的位置有一个数字,则此数字被复制到结果字符串中。小数点前最左边的“0”的位置和小数点后最右边的“0”的位置肯定总在结果字符串中出现的数字范围。 “00”说明符使得值被舍入到小数点前最近的数字,其中零位总被舍去。
数字占位符: 若是格式化的值在格式字符串中出现“#”的位置有一个数字,则此数字被复制到结果字符串中。不然,结果字符串中的此位置不存储任何值。
请注意,若是“0”不是有效数字,此说明符永不显示“0”字符,即便“0”是字符串中惟一的数字。若是“0”是所显示的数字中的有效数字,则显示“0”字符。 “##”格式字符串使得值被舍入到小数点前最近的数字,其中零总被舍去。
复制代码 代码以下:
string.Format("{0:d}",System.DateTime.Now) //结果为:2009-3-20 (月份位置不是03)
string.Format("{0:D}",System.DateTime.Now) //结果为:2009年3月20日
string.Format("{0:f}",System.DateTime.Now) //结果为:2009年3月20日 15:37
string.Format("{0:F}",System.DateTime.Now) //结果为:2009年3月20日 15:37:52
string.Format("{0:g}",System.DateTime.Now) //结果为:2009-3-20 15:38
string.Format("{0:G}",System.DateTime.Now) //结果为:2009-3-20 15:39:27
string.Format("{0:m}",System.DateTime.Now) //结果为:3月20日
string.Format("{0:t}",System.DateTime.Now) //结果为:15:41
string.Format("{0:T}",System.DateTime.Now) //结果为:15:41:50
原文:https://www.cnblogs.com/freeliver54/p/6380719.html
2009年發行ASP.NET MVC 1.0版
2010年發行ASP.NET MVC 2.0版,VS2010
2011年發行ASP.NET MVC 3.0版+EF4,须要.Net4.0支持,VS2011
2012年發行ASP.NET MVC 4.0版+EF5,须要.Net4.0支持,VS2012
2013年發行ASP.NET MVC 5.0版+EF6,须要.Net4.5支持,VS2013
2015年發行ASP.NET MVC 6.0版+EF7,须要.Net5.0支持,VS2015
ASP.NET 5.0 将更名为 ASP.NET Core 1.0
ASP.NET MVC 6 将更名为 ASP.NET MVC Core 1.0
Entity Framework 7.0 将更名为 Entity Framework Core 1.0
https://blog.csdn.net/PacosonSWJTU/article/details/52786216
1.什么是控制? 以下图所示,咱们看到了 软件系统中 对象的 高耦合现象。全体齿轮的转动由一个对象来控制,如类B。
2.什么是 控制反转? 是用来对对象进行解耦。借助第三方实现具备依赖关系的的对象之间的解耦。这个第三方就是 ioc 容器。引入了 ioc 容器后,对象 A、B、C、D 之间没有了依赖关系,全体齿轮转动的控制权交给 容器。这时候齿轮转动控制权不属于任何对象,而属于ioc 容器,因此控制权反转了,从 某个对象 转到了 ioc 容器。
3. 什么是依赖注入?
3.1 什么是依赖?依赖就是指一种关系,若是 在类A中 建立了 类B的实例,咱们说 类A 依赖 类B。
3.2 看个荔枝:
public class class A{
B b;
public A(){
b = new B();
}
void func(){
b.func();
}
}
出现的问题(problems):
解决方法:
public class class A{
B b;
public A(B b){
this.b = b;
}
void func(){
b.func();
}
}
3.3)依赖注入定义: 将B对象实例 做为类A的构造器参数进行传入,在调用类A 构造器以前,类B实例已经被初始化好了。像这种非本身主动初始化依赖,而经过外部传入依赖对象的方式,咱们就称为依赖注入。
下面是工厂方法实现依赖注入
当咱们调用的时候直接讲用工厂方法,让工厂方法去new出对象,与咱们脱离关系
Class C {
J j ;
Human h = new Human;
j=Human.getJ();
}
4.依赖反转
4.1 根据依赖注入的定义: 被依赖者对象并非依赖者本身主动初始化,而是经过外部传入被依赖者的方式,那么被依赖者对象类型 能够是 其 自己,也可使其实现类或继承子类;
4.2 因此,通过分析,被依赖者的对象类型 并非 依赖者自身能够决定的,(固然传统的程序设计方式是依赖者 决定的),而是由外部建立者决定的,因此 被依赖者类型的决定权反转了。对于spirng来讲 ,就是由 spring容器决定的;
4.3 依赖反转定义:被依赖者的 对象类型 并非由依赖者自身能够决定的,而是由 外部建立者决定的,外部传入什么类型的对象 就是 什么类型的对象 , 依赖者对其一无所知;
=======================================================================================
【AOP】
转自:http://docs.jboss.org/aop/1.0/aspect-framework/userguide/en/html/what.html
【1】What is it? 面向切面编程是什么?
1)切面定义:一个切面是一般指散布在方法,类,对象层次结构或甚至整个对象模型中的共同特征。 它是看起来和睦味的行为差很少,它应该有结构,但你不能用代码在传统的面向对象技术中表示这种结构。
2)切面荔枝:例如,度量(时间,用于度量运行某个代码片须要花费多长时间)是一个常见切面。 要从应用程序生成有用的日志,您必须(一般自由地)在代码中散布信息性消息。 然而,度量是你的类或对象模型真正不该该关注的东西。 毕竟,度量与您的实际应用无关:它不表明客户或账户,而且不实现业务规则。 它只是正交。
3)横切关注点定义:在AOP中,像度量这样的特征称为横切关注点,由于它是一种“截断”对象模型中多个点但仍然大相径庭的行为。做为一种开发方法,AOP建议您抽象和封装横切关注点。
4)横切关注点荔枝:例如,假设您想要向应用程序添加代码以测量调用特定方法所需的时间。 在纯Java中,代码看起来像下面这样。
public class BankAccountDAO {
public void withdraw(double amount) {
long startTime = System.currentTimeMillis();
try {
// Actual method body...
}
finally {
long endTime = System.currentTimeMillis() - startTime;
System.out.println("withdraw took: " + endTime);
}
}
}
代码分析)虽然这段代码工做,这个方法有一些问题:
这种度量方法很难维护,扩展和扩展,由于它分散在整个代码库中。 这只是一个很小的例子! 在许多状况下,OOP可能不老是向类添加指标的最佳方法。
面向方面的编程提供了一种封装这种类型的行为功能的方法。 它容许您添加行为,如度量“围绕”您的代码。 例如,AOP为您提供了程序控制,以指定您但愿在执行代码的实际主体以前调用BankAccountDAO来执行度量方面。
【2】在JBoss 切面编程中建立切面
1)简而言之,全部AOP框架定义了两个东西:一种实现横切关注点的方法,以及一个编程语言或一组标签 -以指定如何应用这些代码片断。(一种是 定义横切关注点的方法,二是指定该横切关注点在何处使用)
2)让咱们来看看JBoss AOP,它的横切关注点,以及如何在JBoss中实现一个度量方面。
在JBoss AOP中建立度量方面的第一步是将度量特性封装在本身的Java类中。 清单二将清单One的BankAccountDAO.withdraw()方法中的try / finally块提取为Metrics,这是一个JBoss AOP拦截器类的实现。
清单二:在JBoss AOP拦截器中实现度量
public class Metrics implements org.jboss.aop.Interceptor
03. public Object invoke(Invocation invocation) throws Throwable {
05. long startTime = System.currentTimeMillis();
06. try {
08. return invocation.invokeNext();
09. }
10. finally {
12. long endTime = System.currentTimeMillis() - startTime;
13. java.lang.reflect.Method m = ((MethodInvocation)invocation).method;
14. System.out.println("method " + m.toString() + " time: " + endTime + "ms");
15. }
16. }
17. }
对以上代码的分析:
【3】JBoss AOP中切面的应用
要应用一个方面,您定义什么时候执行方面代码。 这些执行点被称为切入点。 相似于切入点是一个正则表达式。 正则表达式匹配字符串时,切入点表达式匹配应用程序中的事件/点。 例如,有效的切入点定义将是“对于JDBC方法executeQuery()的全部调用,调用验证SQL语法的方面”。
个人总结(AOP)
1)切面的抽象型定义:切面是散布在 方法,类,对象层次结构或 整个对象模型中的共同特征;(是一组特征)
2)切面荔枝:如 咱们要对某方法的执行时间进行统计,代码以下:
public class BankAccountDAO {
public void withdraw(double amount) {
long startTime = System.currentTimeMillis();
try {
// Actual method body...
}
finally {
long endTime = System.currentTimeMillis() - startTime;
System.out.println("withdraw took: " + endTime);
}
}
}
其中 有关于统计时间的都是特征,它们的共性都是由于要统计时间而 添加到 方法中的代码块;那因此这些代码块就组成了 切面;
3)以上代码块出现了问题
问题1)代码重用率低:必须手动将该 切面代码添加到 要统计方法执行时间的方法中;
问题2)代码膨胀和可读性低:计时代码真的不用你去关心,这些切面代码只会让你的代码更加 膨胀和难读,由于你必须在一个try / finally块中包含时间, 但这个时间与咱们 方法的执行没有任何关系;
问题3)不利于代码扩展(代码耦合性高):若是要扩展此功能以包括方法或失败计数,或者甚至将这些统计信息注册到更复杂的报告机制,则必须再次修改许多不一样的文件;
最后的最后:这种时间度量方法很难维护,扩展和扩展,由于它分散在整个代码库中;
4)aop 是干什么的?
4.1)intro:面向切面编程提供了一种封装切面行为的方法。AOP提供了程序控制,以指定您但愿在执行代码的实际主体以前调用 时间统计方法 来执行执行时间度量。
4.2)全部AOP框架定义了两个东西:一种实现横切关注点的方法,以及一个编程语言或一组标签 -以指定如何应用这些代码片断。(一种是 定义横切关注点的方法,二是指定该横切关注点在什么时候何处使用)
5)如何定义aop中横切关注点的方法 和 指定该横切关注点的应用地点
5.1)定义横切关注点:将切面代码度量特性封装在本身的Java类中
public class Metrics implements org.jboss.aop.Interceptor
public Object invoke(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return invocation.invokeNext(); // 调用真正的实体方法
}
finally {
long endTime = System.currentTimeMillis() - startTime;
java.lang.reflect.Method m = ((MethodInvocation)invocation).method;
System.out.println("method " + m.toString() + " time: " + endTime + "ms");
}
}
}
5.2)横切关注点在什么时候何地使用?
要应用一个切面,您定义什么时候执行切面代码。 这些执行点被称为切入点。切入点相似因而一个正则表达式。 正则表达式匹配字符串时,切入点表达式匹配应用程序中的事件/点。
看个 切入点的定义: @Before("execution(** concert.Performance.perform(..))")
https://blog.csdn.net/nic7968/article/details/14162523
本文目录:
在[ASP.NET MVC 小牛之路]系列的理解MVC模式文章中,咱们提到MVC的一个重要特征是关注点分离(separation of concerns)。咱们但愿应用程序的各部分组件尽量多的相互独立、尽量少的相互依赖。
咱们的理想状况是:一个组件能够不知道也能够不关心其余的组件,但经过提供的公开接口却能够实现其余组件的功能调用。这种状况就是所谓的松耦合。
举个简单的例子。咱们要为商品定制一个“高级”的价钱计算器LinqValueCalculator,这个计算器须要实现IValueCalculator接口。以下代码所示:
public interface IValueCalculator {
decimal ValueProducts(params Product[] products);
}
public class LinqValueCalculator : IValueCalculator {
public decimal ValueProducts(params Product[] products) {
return products.Sum(p => p.Price);
}
}
Product类和前两篇博文中用到的是同样的。如今有个购物车ShoppingCart类,它须要有一个能计算购物车内商品总价钱的功能。但购物车自己没有计算的功能,所以,购物车要嵌入一个计算器组件,这个计算器组件能够是LinqValueCalculator组件,但不必定是LinqValueCalculator组件(之后购物车升级,可能会嵌入别的更高级的计算器)。那么咱们能够这样定义购物车ShoppingCart类:
1 public class ShoppingCart { 2 //计算购物车内商品总价钱 3 public decimal CalculateStockValue() { 4 Product[] products = { 5 new Product {Name = "西瓜", Category = "水果", Price = 2.3M}, 6 new Product {Name = "苹果", Category = "水果", Price = 4.9M}, 7 new Product {Name = "空心菜", Category = "蔬菜", Price = 2.2M}, 8 new Product {Name = "地瓜", Category = "蔬菜", Price = 1.9M} 9 };10 IValueCalculator calculator = new LinqValueCalculator();11 12 //计算商品总价钱 13 decimal totalValue = calculator.ValueProducts(products);14 15 return totalValue;16 }17 }
ShoppingCart类是经过IValueCalculator接口(而不是经过LinqValueCalculator)来计算商品总价钱的。若是之后购物车升级须要使用更高级的计算器,那么只须要改变第10行代码中new后面的对象(即把LinqValueCalculator换掉),其余的代码都不用变更。这样就实现了必定的松耦合。这时三者的关系以下图所示:
这个图说明,ShoppingCart类既依赖IValueCalculator接口又依赖LinqValueCalculator类。这样就有个问题,用现实世界的话来说就是,若是嵌入在购物车内的计算器组件坏了,会致使整个购物车不能正常工做,岂不是要把整个购物车要换掉!最好的办法是将计算器组件和购物车彻底独立开来,这样无论哪一个组件坏了,只要换对应的组件便可。即咱们要解决的问题是,要让ShoppingCart组件和LinqValueCalculator组件彻底断开关系,而依赖注入这种设计模式就是为了解决这种问题。
上面实现的部分松耦合显然并非咱们所须要的。咱们所须要的是,在一个类内部,不经过建立对象的实例而可以得到某个实现了公开接口的对象的引用。这种“须要”,就称为DI(依赖注入,Dependency Injection),和所谓的IoC(控制反转,Inversion of Control )是一个意思。
DI是一种经过接口实现松耦合的设计模式。初学者可能会好奇网上为何有那么多技术文章对DI这个东西大兴其笔,是由于DI对于基于几乎全部框架下,要高效开发应用程序,它都是开发者必需要有的一个重要的理念,包括MVC开发。它是解耦的一个重要手段。
DI模式可分为两个部分。一是移除对组件(上面示例中的LinqValueCalculator)的依赖,二是经过类的构造函数(或类的Setter访问器)来传递实现了公开接口的组件的引用。以下面代码所示:
public class ShoppingCart {
IValueCalculator calculator;
//构造函数,参数为实现了IEmailSender接口的类的实例
public ShoppingCart(IValueCalculator calcParam) {
calculator = calcParam;
}
//计算购物车内商品总价钱
public decimal CalculateStockValue() {
Product[] products = {
new Product {Name = "西瓜", Category = "水果", Price = 2.3M},
new Product {Name = "苹果", Category = "水果", Price = 4.9M},
new Product {Name = "空心菜", Category = "蔬菜", Price = 2.2M},
new Product {Name = "地瓜", Category = "蔬菜", Price = 1.9M}
};
//计算商品总价钱
decimal totalValue = calculator.ValueProducts(products);
return totalValue;
}
}
这样咱们就完全断开了ShoppingCart和LinqValueCalculator之间的依赖关系。某个实现了IValueCalculator接口的类(示例中的MyEmailSender)的实例引用做为参数,传递给ShoppingCart类的构造函数。可是ShoppingCart类不知道也不关心这个实现了IValueCalculator接口的类是什么,更没有责任去操做这个类。 这时咱们能够用下图来描述ShoppingCart、LinqValueCalculator和IValueCalculator之间的关系:
在程序运行的时候,依赖被注入到ShoppingCart,这个依赖就是,经过ShoppingCart构造函数传递实现了IValueCalculator接口的类的实例引用。在程序运行以前(或编译时),ShoppingCart和任何实现IValueCalculator接口的类没有任何依赖关系。(注意,程序运行时是有具体依赖关系的。)
注意,上面示例使用的注入方式称为“构造注入”,咱们也能够经过属性来实现注入,这种注入被称为“setter 注入”,就不举例了,朋友们能够看看T2噬菌体的文章依赖注入那些事儿来对DI进行更多的了解。
因为常常会在编程时使用到DI,因此出现了一些DI的辅助工具(或叫DI容器),如Unity和Ninject等。因为Ninject的轻量和用简单,加上本人只用过Ninject,因此本系列文章选择用它来开发MVC应用程序。下面开始介绍Ninject,但在这以前,先来介绍一个安装Ninject须要用到的插件-NuGet。
NuGet 是一种 Visual Studio 扩展,它可以简化在 Visual Studio 项目中添加、更新和删除库(部署为程序包)的操做。好比你要在项目中使用Log4Net这个库,若是没有NuGet这个扩展,你可能要先到网上搜索Log4Net,再将程序包的内容解压缩到解决方案中的特定位置,而后在各项目工程中依次添加程序集引用,最后还要使用正确的设置更新 web.config。而NuGet能够简化这一切操做。例如咱们在讲依赖注入的项目中,若要使用一个NuGet库,可直接右击项目(或引用),选择“管理NuGet程序包”(VS2010下为“Add Library Package Reference”),以下图:
在弹出以下窗口中选择“联机”,搜索“Ninject”,而后进行相应的操做便可:
在本文中咱们只须要知道如何使用NuGet来安装库就能够了。NuGet的详细使用方法可查看MSDN文档:使用 NuGet 管理项目库。
在使用Ninject前先要建立一个Ninject内核对象,代码以下:
class Program {
static void Main(string[] args) {
//建立Ninject内核实例
IKernel ninjectKernel = new StandardKernel();
}
}
使用Ninject内核对象通常可分为两个步骤。第一步是把一个接口(IValueCalculator)绑定到一个实现该接口的类(LinqValueCalculator),以下:
...//绑定接口到实现了该接口的类
ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator<();
...
这个绑定操做就是告诉Ninject,当接收到一个请求IValueCalculator接口的实现时,就返回一个LinqValueCalculator类的实例。
第二步是用Ninject的Get方法去获取IValueCalculator接口的实现。这一步,Ninject将自动为咱们建立LinqValueCalculator类的实例,并返回该实例的引用。而后咱们能够把这个引用经过构造函数注入到ShoppingCart类。以下代码所示:
...// 得到实现接口的对象实例
IValueCalculator calcImpl = ninjectKernel.Get<IValueCalculator>(); // 建立ShoppingCart实例并注入依赖
ShoppingCart cart = new ShoppingCart(calcImpl); // 计算商品总价钱并输出结果
Console.WriteLine("Total: {0:c}", cart.CalculateStockValue());
...
Ninject的使用的通常步骤就是这样。该示例可正确输出以下结果:
但看上去Ninject的使用好像使得编码变得更加烦琐,朋友们会问,直接使用下面的代码不是更简单吗:
...
IValueCalculator calcImpl = new LinqValueCalculator();
ShoppingCart cart = new ShoppingCart(calcImpl);
Console.WriteLine("Total: {0:c}", cart.CalculateStockValue());
...
的确,对于单个简单的DI,用Ninject确实显得麻烦。但若是添加多个复杂点的依赖关系,使用Ninject则可大大提升编码的工做效率。
当咱们请求Ninject建立某个类型的实例时,它会检查这个类型和其它类型之间的耦合关系。若是存在依赖关系,那么Ninject会根据依赖处理理它们,并建立全部所需类的实例。为了解释这句话和说明使用Ninject编码的便捷,咱们再建立一个接口IDiscountHelper和一个实现该接口的类DefaultDiscountHelper,代码以下:
//折扣计算接口public interface IDiscountHelper {
decimal ApplyDiscount(decimal totalParam);
}
//默认折扣计算器public class DefaultDiscountHelper : IDiscountHelper {
public decimal ApplyDiscount(decimal totalParam) {
return (totalParam - (1m / 10m * totalParam));
}
}
IDiscounHelper接口声明了ApplyDiscount方法,DefaultDiscounterHelper实现了该接口,并定义了打9折的ApplyDiscount方法。而后咱们能够把IDiscounHelper接口做为依赖添加到LinqValueCalculator类中。代码以下:
public class LinqValueCalculator : IValueCalculator {
private IDiscountHelper discounter;
public LinqValueCalculator(IDiscountHelper discountParam) {
discounter = discountParam;
}
public decimal ValueProducts(params Product[] products) {
return discounter.ApplyDiscount(products.Sum(p => p.Price));
}
}
LinqValueCalculator类添加了一个用于接收IDiscountHelper接口的实现的构造函数,而后在ValueProducts方法中调用该接口的ApplyDiscount方法对计算出的商品总价钱进行打折处理,并返回折后总价。
到这,咱们先来画个图理一理ShoppingCart、LinqValueCalculator、IValueCalculator以及新添加的IDiscountHelper和DefaultDiscounterHelper之间的关系:
以此,咱们还能够添加更多的接口和实现接口的类,接口和类愈来愈多时,它们的关系图看上去会像一个依赖“链”,和生物学中的分子结构图差很少。
按照前面说的使用Ninject的“二个步骤”,如今咱们在Main中的方法中编写用于计算购物车中商品折后总价钱的代码,以下所示:
1 class Program { 2 static void Main(string[] args) { 3 IKernel ninjectKernel = new StandardKernel(); 4 5 ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>(); 6 ninjectKernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>(); 7 8 IValueCalculator calcImpl = ninjectKernel.Get<IValueCalculator>(); 9 ShoppingCart cart = new ShoppingCart(calcImpl);10 Console.WriteLine("Total: {0:c}", cart.CalculateStockValue());11 Console.ReadKey();12 }13 }
输出结果:
代码一目了然,虽然新添加了一个接口和一个类,但Main方法中只增长了第6行一句代码,获取实现IValueCalculator接口的对象实例的代码不须要作任何改变。
定位到代码的第8行,这一行代码,Ninject为咱们作的事是:
当咱们须要使用IValueCalculator接口的实现时(经过Get方法),它便为咱们建立LinqValueCalculator类的实例。而当建立LinqValueCalculator类的实例时,它检查到这个类依赖IDiscountHelper接口。因而它又建立一个实现了该接口的DefaultDiscounterHelper类的实例,并经过构造函数把该实例注入到LinqValueCalculator类。而后返回LinqValueCalculator类的一个实例,并赋值给IValueCalculator接口的对象(第8行的calcImpl)。
总之,无论依赖“链”有多长有多复杂,Ninject都会按照上面这种方式检查依赖“链”上的每一个接口和实现接口的类,并自动建立所须要的类的实例。在依赖“链”越长越复杂的时候,更能显示使用Ninject编码的高效率。
我我的将Ninject的绑定方式分为:通常绑定、指定值绑定、自我绑定、派生类绑定和条件绑定。这样分类有点牵强,只是为了本文的写做须要和方便读者阅读而分,并非官方的分类。
一、通常绑定
在前文的示例中用Bind和To方法把一个接口绑定到实现该接口的类,这属于通常的绑定。经过前文的示例相信你们已经掌握了,在这就再也不累述。
二、指定值绑定
咱们知道,经过Get方法,Ninject会自动帮咱们建立咱们所须要的类的实例。但有的类在建立实例时须要给它的属性赋值,以下面咱们改造了一下的DefaultDiscountHelper类:
public class DefaultDiscountHelper : IDiscountHelper {
public decimal DiscountSize { get; set; }
public decimal ApplyDiscount(decimal totalParam) {
return (totalParam - (DiscountSize / 10m * totalParam));
}
}
给DefaultDiscountHelper类添加了一个DiscountSize属性,实例化时须要指定折扣值(DiscountSize属性值),否则ApplyDiscount方法就没意义。而实例化的动做是Ninject自动完成的,怎么告诉Ninject在实例化类的时候给某属性赋一个指定的值呢?这时就须要用到参数绑定,咱们在绑定的时候能够经过给WithPropertyValue方法传参的方式指定DiscountSize属性的值,以下代码所示:
public static void Main() {
IKernel ninjectKernel = new StandardKernel();
ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
ninjectKernel.Bind<IDiscountHelper>()
.To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize", 5M);
IValueCalculator calcImpl = ninjectKernel.Get<IValueCalculator>();
ShoppingCart cart = new ShoppingCart(calcImpl);
Console.WriteLine("Total: {0:c}", cart.CalculateStockValue());
Console.ReadKey();
}
只是在Bind和To方法后添加了一个WithPropertyValue方法,其余代码都不用变,再一次见证了用Ninject编码的高效。
WithPropertyValue方法接收了两个参数,一个是属性名(示例中的"DiscountSize"),一个是属性值(示例中的5)。运行结果以下:
若是要给多个属性赋值,则能够在Bind和To方式后添加多个WithPropertyValue(<属性名>,<属性值>)方法。
咱们还能够在类的实例化的时候为类的构造函数传递参数。为了演示,咱们再把DefaultDiscountHelper类改一下:
public class DefaultDiscountHelper : IDiscountHelper {
private decimal discountRate;
public DefaultDiscountHelper(decimal discountParam) {
discountRate = discountParam;
}
public decimal ApplyDiscount(decimal totalParam) {
return (totalParam - (discountRate/ 10m * totalParam));
}
}
显然,DefaultDiscountHelper类在实例化的时候必须给构造函数传递一个参数,否则程序会出错。和给属性赋值相似,只是用的方法是WithConstructorArgument(<参数名>,<参数值>),绑定方式以下代码所示:
...
ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
ninjectKernel.Bind<IDiscountHelper>()
.To< DefaultDiscountHelper>().WithConstructorArgument("discountParam", 5M);
...
一样,只须要更改一行代码,其余代码原来怎么写仍是怎么写。若是构造函数有多个参数,则需在Bind和To方法后面加上多个WithConstructorArgument便可。
3.自我绑定
Niject的一个很是好用的特性就是自绑定。当经过Bind和To方法绑定好接口和类后,能够直接经过ninjectKernel.Get<类名>()来得到一个类的实例。
在前面的几个示例中,咱们都是像下面这样来建立ShoppingCart类实例的:
...
IValueCalculator calcImpl = ninjectKernel.Get<IValueCalculator>();
ShoppingCart cart = new ShoppingCart(calcImpl);
...
其实有一种更简单的定法,以下:
...
ShoppingCart cart = ninjectKernel.Get<ShoppingCart>();
...
这种写法不须要关心ShoppingCart类依赖哪一个接口,也不须要手动去获取该接口的实现(calcImpl)。当经过这句代码请求一个ShoppingCart类的实例的时候,Ninject会自动判断依赖关系,并为咱们建立所需接口对应的实现。这种方式看起来有点怪,其实中规中矩的写法是:
...
ninjectKernel.Bind<ShoppingCart>().ToSelf();
ShoppingCart cart = ninjectKernel.Get<ShoppingCart>();
...
这里有自我绑定用的是ToSelf方法,在本示例中能够省略该句。但用ToSelf方法自我绑定的好处是能够在其后面用WithXXX方法指定构造函数参数、属性等等的值。
4.派生类绑定
经过通常绑定,当请求一个接口的实现时,Ninject会帮咱们自动建立实现接口的类的实例。咱们说某某类实现某某接口,也能够说某某类继承某某接口。若是咱们把接口看成一个父类,是否是也能够把父类绑定到一个继承自该父类的子类呢?咱们来实验一把。先改造一下ShoppingCart类,给它的CalculateStockValue方法改为虚方法:
public class ShoppingCart {
protected IValueCalculator calculator;
protected Product[] products;
//构造函数,参数为实现了IEmailSender接口的类的实例
public ShoppingCart(IValueCalculator calcParam) {
calculator = calcParam;
products = new[]{
new Product {Name = "西瓜", Category = "水果", Price = 2.3M},
new Product {Name = "苹果", Category = "水果", Price = 4.9M},
new Product {Name = "空心菜", Category = "蔬菜", Price = 2.2M},
new Product {Name = "地瓜", Category = "蔬菜", Price = 1.9M}
};
}
//计算购物车内商品总价钱
public virtual decimal CalculateStockValue() {
//计算商品总价钱
decimal totalValue = calculator.ValueProducts(products);
return totalValue;
}
}
再添加一个ShoppingCart类的子类:
public class LimitShoppingCart : ShoppingCart {
public LimitShoppingCart(IValueCalculator calcParam)
: base(calcParam) {
}
public override decimal CalculateStockValue() {
//过滤价格超过了上限的商品
var filteredProducts = products.Where(e => e.Price < ItemLimit);
return calculator.ValueProducts(filteredProducts.ToArray());
}
public decimal ItemLimit { get; set; }
}
而后把父类ShoppingCart绑定到子类LimitShoppingCart:
public static void Main() {
IKernel ninjectKernel = new StandardKernel();
ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
ninjectKernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>()
.WithPropertyValue("DiscountSize", 5M);
//派生类绑定
ninjectKernel.Bind<ShoppingCart>().To<LimitShoppingCart>()
.WithPropertyValue("ItemLimit", 3M);
ShoppingCart cart = ninjectKernel.Get<ShoppingCart>();
Console.WriteLine("Total: {0:c}", cart.CalculateStockValue());
Console.ReadKey();
}
运行结果:
从运行结果能够看出,cart对象调用的是子类的CalculateStockValue方法,证实了能够把父类绑定到一个继承自该父类的子类。经过派生类绑定,当咱们请求父类的时候,Ninject自动帮咱们建立一个对应的子类的实例,并将其返回。因为抽象类不能被实例化,因此派生类绑定在使用抽象类的时候很是有用。
5.条件绑定
当一个接口有多个实现或一个类有多个子类的时候,咱们能够经过条件绑定来指定使用哪个实现或子类。为了演示,咱们给IValueCalculator接口再添加一个实现,以下:
public class IterativeValueCalculator : IValueCalculator {
public decimal ValueProducts(params Product[] products) {
decimal totalValue = 0;
foreach (Product p in products) {
totalValue += p.Price;
}
return totalValue;
}
}
IValueCalculator接口如今有两个实现:IterativeValueCalculator和LinqValueCalculator。咱们能够指定,若是是把该接口的实现注入到LimitShoppingCart类,那么就用IterativeValueCalculator,其余状况都用LinqValueCalculator。以下所示:
public static void Main() {
IKernel ninjectKernel = new StandardKernel();
ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
ninjectKernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>()
.WithPropertyValue("DiscountSize", 5M);
//派生类绑定
ninjectKernel.Bind<ShoppingCart>().To<LimitShoppingCart>()
.WithPropertyValue("ItemLimit", 3M);
//条件绑定
ninjectKernel.Bind<IValueCalculator>()
.To<IterativeValueCalculator>().WhenInjectedInto<LimitShoppingCart>();
ShoppingCart cart = ninjectKernel.Get<ShoppingCart>();
Console.WriteLine("Total: {0:c}", cart.CalculateStockValue());
Console.ReadKey();
}
运行结果:
运行结果是6.4,说明没有打折,即调用的是计算方法是IterativeValueCalculator的ValueProducts方法。可见,Ninject会查找最匹配的绑定,若是没有找到条件绑定,则使用默认绑定。在条件绑定中,除了WhenInjectedInto方法,还有When和WhenClassHas等方法,朋友们能够在使用的时候再慢慢研究。
本文用控制台应用程序演示了Ninject的使用,但要把Ninject集成到ASP.NET MVC中仍是有点复杂的。首先要作的事就是建立一个继承System.Web.Mvc.DefaultControllerFactory的类,MVC默认使用这个类来建立Controller类的实例(后续博文会专门讲这个)。代码以下:
using System;using Ninject;using System.Web.Mvc;using System.Web.Routing;
namespace MvcApplication1 {
public class NinjectControllerFactory : DefaultControllerFactory {
private IKernel ninjectKernel;
public NinjectControllerFactory() {
ninjectKernel = new StandardKernel();
AddBindings();
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) {
return controllerType == null ? null : (IController)ninjectKernel.Get(controllerType);
}
private void AddBindings() {
// 在这添加绑定,
// 如:ninjectKernel.Bind<IProductRepository>().To<FakeProductRepository>(); }
}
}
NinjectControllerFactory
如今暂时不解释这段代码,你们都看懂就看,看不懂就过,只要知道在ASP.NET MVC中使用Ninject要作这么一件事就行。
添加完这个类后,还要作一件事,就是在MVC框架中注册这个类。通常咱们在Global.asax文件中的Application_Start方法中进行注册,以下所示:
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
}
注册后,MVC框架就会用NinjectControllerFactory类去获取Cotroller类的实例。在后续博文中会具体演示如何在ASP.NET MVC中使用Ninject,这里就不具体演示了,你们知道须要作这么两件事就行。
虽然咱们前面花了很大功夫来学习Ninject就是为了在MVC中使用这样一个NinjectControllerFactory类,可是了解Ninject如何工做是很是有必要的。理解好了一种DI容器,可使得开发和测试更简单、更高效。
LINQ的查询只在结果被使用,被枚举的状况才执行。参见下面的例子,体会。
public ViewResult FindProducts() {
Product[] products = {
new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
};
var foundProducts = products.OrderByDescending(e => e.Price)
.Take(3)
.Select(e => new {
e.Name,
e.Price
});
products[2] = new Product { Name = "Stadium", Price = 79600M };
StringBuilder result = new StringBuilder();
foreach (var p in foundProducts) {
result.AppendFormat("Price: {0} ", p.Price);
}
return View("Result", (object)result.ToString());
}
..
<tr>
<td>Stock Level</td>
<td>
@if (ViewBag.ProductCount == 0) {
@:Out of Stock
Figure 5-12. Using a switch statement in a Razor view
Chapter 5 ■ Working With razor
115
} else if (ViewBag.ProductCount == 1) {
<b>Low Stock (@ViewBag.ProductCount)</b>
} else {
@ViewBag.ProductCount
}
</td>
</tr>
在C#语句中不须要加@,但在返回的html体里须要加@
namespace UrlsAndRoutes {
public class RouteConfig {
public static void RegisterRoutes(RouteCollection routes) {
routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index",
id = "DefaultId" });
}
}
}
该路由有三个默认值,所以该规则匹配任何0到三个URL字段的url。匹配的原则是从左到右匹配。
URL |
controller |
action |
id |
Home |
Index |
DefaultId |
|
a |
Index |
DefaultId |
|
http://loca:8081/a/b |
a |
b |
DefaultId |
http://loca:8081/a/b/c |
a |
b |
c |
@Html.ActionLink("This is an outgoing URL", //连接显示文本
"Index", "Home", null, // /Action/Controller,其余//=null
new {id = "myAnchorID",@class = "myCSSClass"} //属性 id= “”,class=””
)
OnAuthenticationChallenge 在全部其余filters前调用。
调用时机:1)在鉴权失败(OnAuthentication设置鉴权失败)时会调用,触发用户质询,见3流程。成功不会调用,因此6后没有调用。
2)在Action执行完成后,Result返回前调用。此Action即被Authentication属性修饰的action.见流程8
Pro Asp.net mvc 5一章的执行流程以下:
The sequence is authentication filters, authorization filters,
action filters, and then result filters. The framework executes exception filters at any stage if there is an unhandled exception.
public static MvcHtmlString DisplayMessage(this HtmlHelper html, string msg) {
string result = String.Format("This is the message: <p>{0}</p>", msg);
return new MvcHtmlString(result);
}
result被MvcHtmlString处理后,直接被当成html标签字符串。所以,静razor渲染后,就变成了以下:
public static string DisplayMessage(this HtmlHelper html, string msg) {
return String.Format("This is the message: <p>{0}</p>", msg);
}
而此处,直接传递给razor是字符串,鉴于razor的智能化,它认为是要渲染以下的字符串,因此,就本身encode处理后,变成纯字符串展现:
分而治之,部分须要按字符串展现如:<input>,部分须要安装html标签语言展现如:<p><p>,所以分别处理,这样p标签被展现成段落,而input被展现成纯字符串;
public static MvcHtmlString DisplayMessage(this HtmlHelper html,
string msg)
{
string encodedMessage = html.Encode(msg);
string result = String.Format("This is the message: <p>{0}</p>", encodedMessage);
return new MvcHtmlString(result);
}
21 HtmlHelper
内联自定义的helper能够推断参数的类型;而外部helper须要显示cast