在net中json序列化与反序列化 面向对象六大原则 (第一篇) 一步一步带你了解linq to Object 10分钟浅谈泛型协变与逆变
在net中json序列化与反序列化
准备好饮料,我们一起来玩玩JSON,什么是Json:一种数据表示形式,JSON:JavaScript Object Notation对象表示法
Json语法规则:
-
数据在键值对中
-
数据由逗号分隔
-
花括号保存对象
-
方括号保存数组
一、JSON的表现形式
在javascript中对象的表现形式如下
1.对象表现形式:
<script type="text/javascript">
var jsonObject={code:0,resultmsg:'成功'};
alert(jsonObject.code);
</script>
2.数组表现形式:
<script type="text/javascript">
var personInfo=[
{name:'张三',Age:18,sex:'男'},
{name:'小倩',Age:19,sex:'女'},
{name:'小明',Age:18,sex:'男'}
];
alert(personInfo[0].name);
</script>
3.对象数组联合表现形式:
<script type="text/javascript">
var Studennt=[
{name:'张三',Age:18,sex:'男',succes:[
{name:'语文',succ:89.5},
{name:'数学',succ:89.5},
{name:'外语',succ:89.5}
]
},
{name:'小倩',Age:19,sex:'女',succes:[
{name:'语文',succ:89.5},
{name:'数学',succ:89.5},
{name:'外语',succ:89.5}
]},
{name:'小明',Age:18,sex:'男',succes:[
{name:'语文',succ:89.5},
{name:'数学',succ:89.5},
{name:'外语',succ:89.5}
]}
];
alert(Studennt[0].name);
</script>
二、什么是JSON序列化与反序列化?
序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。
简单来说就是对象转换成为JSON格式字符串我们称为序列化,反之JSON字符串转换为对象我们称为反序列化。
下边我们准备点基础类,部门信息类
using System;
namespace MySerialize.Entity
{
public class DeptInfo
{
public int Id { get; set; }
public string DeptName { get; set; }
public DateTime CreateTime { get; set; }
}
}
枚举类,分别是职务类别,人员状态。这里简单说下枚举的特点是如果枚举值不指定起始位置默认从0开始,如果指定枚举第一个值则从指定的第一位开始一次递增加一计算枚举值,如果每个枚举值都赋值则按赋值计算,
不理解这句话没关系不影响学习JSON正反序列化。
using System;
namespace MySerialize.Entity
{
/// <summary>
/// 职务类别
/// </summary>
public enum JobType
{
董事长,
总经理,
总监,
部门经理,
人事专员,
职员,
工程师,
其他
}
public enum PersonType
{
在职,离职,退休,停职
}
}
用户信息类
using System;
using System.Collections.Generic;
namespace MySerialize.Entity
{
public class UserInfo
{
public int Id { get; set; }
public string UserName { get; set; }
/// <summary>
/// 姓
/// </summary>
public string FirstName { get; set; }
/// <summary>
/// 名
/// </summary>
public string LastName { get; set; }
public string PassWord { get; set; }
public string Phone { get; set; }
public string Address { get; set; }
public DeptInfo Dept { get; set; }
public JobType DeptType { get; set; }
public PersonType UserType { get; set; }
/// <summary>
/// 职业技能
/// </summary>
public Dictionary<int, string> UserSkill { get; set; }
/// <summary>
/// 特长
/// </summary>
public Dictionary<string, string> UserStrong { get; set; }
}
}
数据初始化类
using MySerialize.Entity;
using System;
using System.Collections.Generic;
namespace MySerialize
{
public class DataFactory
{
/// <summary>
/// 获取所有专业技能
/// </summary>
/// <returns></returns>
public static Dictionary<int, string> GetSkillAll() {
Dictionary<int, string> DictionarySkill = new Dictionary<int, string>();
DictionarySkill.Add(1, "1234");
DictionarySkill.Add(2, "abcd");
DictionarySkill.Add(3, "大中国");
DictionarySkill.Add(4, "学习");
DictionarySkill.Add(5, "上网");
return DictionarySkill;
}
/// <summary>
/// 获取所有的特长
/// </summary>
/// <returns></returns>
public static Dictionary<string, string> GetStrongAll()
{
Dictionary<string, string> DictionaryStrong = new Dictionary<string, string>();
DictionaryStrong.Add("1", "abcdefg");
DictionaryStrong.Add("2", "abcdefg123");
DictionaryStrong.Add("3", "tvbcd");
DictionaryStrong.Add("4", "您吃了吗");
DictionaryStrong.Add("5", "吃了吗您呢");
DictionaryStrong.Add("6", "tvbc 早上好");
DictionaryStrong.Add("7", "vbbc 晚上好");
return DictionaryStrong;
}
public static List<UserInfo> BuildUserList()
{
DeptInfo dept0 = new DeptInfo() { CreateTime = DateTime.Now, DeptName = "开发部", Id = 1 };
DeptInfo dept1 = new DeptInfo() { CreateTime = DateTime.Now, DeptName = "电商事业部", Id = 2 };
DeptInfo dept2 = new DeptInfo() { CreateTime = DateTime.Now, DeptName = "成功部", Id = 3 };
DeptInfo dept3 = new DeptInfo() { CreateTime = DateTime.Now, DeptName = "人力资源管理部", Id = 4 };
List<UserInfo> list = new List<UserInfo>();
list.Add(new UserInfo()
{
Address = "北京昌平",
DeptType = JobType.工程师,
Dept = dept0,
Id = 1,
FirstName = "张",
LastName = "三",
PassWord = "",
Phone = "13122222222222",
UserName = "wbc",
UserType = PersonType.在职,
UserSkill = GetSkillAll(),
UserStrong = GetStrongAll()
});
list.Add(new UserInfo()
{
Address = "北京昌平",
DeptType = JobType.工程师,
Dept = dept0,
Id = 5,
FirstName = "张",
LastName = "三",
PassWord = "",
Phone = "13122222222222",
UserName = "wbc",
UserType = PersonType.在职,
UserSkill = GetSkillAll(),
UserStrong = GetStrongAll()
});
list.Add(new UserInfo()
{
Address = "北京昌平",
DeptType = JobType.工程师,
Dept = dept3,
Id = 3,
FirstName = "张",
LastName = "三",
PassWord = "",
Phone = "13122222222222",
UserName = "wbc",
UserType = PersonType.在职,
UserSkill = GetSkillAll(),
UserStrong = GetStrongAll()
});
list.Add(new UserInfo()
{
Address = "北京昌平",
DeptType = JobType.工程师,
Dept = dept2,
Id = 2,
FirstName = "wwdd",
LastName = "三",
PassWord = "",
Phone = "13122222222222",
UserName = "wbc",
UserType = PersonType.在职,
UserSkill = GetSkillAll(),
UserStrong = GetStrongAll()
});
list.Add(new UserInfo()
{
Address = "北京昌平",
DeptType = JobType.工程师,
Dept = dept1,
Id = 4,
FirstName = "wang",
LastName = "三",
PassWord = "",
Phone = "13122222222222",
UserName = "wbc",
UserType = PersonType.在职,
UserSkill = GetSkillAll(),
UserStrong = GetStrongAll()
});
return list;
}
}
}
三、常见Json序列化和反序列化类
1.DataContractJsonSerializer 类序列化和反序列化:
DataContractJsonSerializer 类在 System.Runtime.Serialization.Json这个命名空间下,在net 4.0里面,而这个命名空间在 System.Runtime.Serialization.dll中
List<UserInfo> list= DataFactory.BuildUserList();//获取基础数据
//对象的系列化
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(List<UserInfo>));
MemoryStream ms = new MemoryStream();
ser.WriteObject(ms, list);
string jsonString = Encoding.UTF8.GetString(ms.ToArray());
ms.Close();
//反序列化
ser = new DataContractJsonSerializer(typeof(List<UserInfo>));
ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonString));
List<UserInfo> obj = (List<UserInfo>)ser.ReadObject(ms);
2.JavaScriptSerializer 类序列化和反序列化:
JavaScriptSerializer 类在 System.Web.Script.Serialization命名空间下了,在net3.0里面,而这个命名空间在 System.Web.Extensions.dll 中
Dictionary<string, string> dictionaryStrong= new Dictionary<string, string>();
dictionaryStrong.Add("1","22222222222222");
UserInfo user = new UserInfo() {
Id=1,
Dept =new DeptInfo() { CreateTime=DateTime.Now,Id=1,DeptName="开发部"},
Address="北京北京市",
DeptType=JobType.工程师,
FirstName="王",
LastName="柏成",
PassWord="",
Phone="13126994771",
UserName="wbc",
UserSkill=null,
UserStrong= dictionaryStrong,
UserType=PersonType.在职
};
//序列化
System.Web.Script.Serialization.JavaScriptSerializer jss = new System.Web.Script.Serialization.JavaScriptSerializer();
string jsonString = jss.Serialize(user);
txtData.Text = jsonString;
//反序列化
UserInfo obj = jss.Deserialize<UserInfo>(jsonString);
3.JsonConvert类序列化和反序列化:
JsonConvert类在 Newtonsoft.Json 命名空间下了,而这个命名空间在Newtonsoft.Json.dll 中,而这个dll 只是第三方的一个中间件,我们可以通过nuget 获取并引用。
List<UserInfo> list = DataFactory.BuildUserList();//获取基础数据
//对象的系列化
string jsonString= JsonConvert.SerializeObject(list);
txtData.Text = jsonString;
//反序列化
List<UserInfo> obj = JsonConvert.DeserializeObject<List<UserInfo>>(jsonString);
4. 简单学习 linq to Json:
linq to Json 是在Newtonsoft.Json.dll 的 Newtonsoft.Json.linq 命名空间下. json 操作类如下
| className(类名) | describe(说明) |
JObject |
用于操作JSON对象 |
JArray |
用语操作JSON数组 |
JValue |
表示数组中的值 |
JProperty |
表示对象中的属性,以"key/value"形式 |
JToken |
用于存放Linq to JSON查询后的结果 |
创建Json对象和数组组合:
JArray jArray = new JArray();
JObject staff = new JObject();
staff.Add(new JProperty("Name", "wangbaicheng"));
staff.Add(new JProperty("Age", 33));
staff.Add(new JProperty("Department", "销售部"));
staff.Add(new JProperty("Leader", new JObject(new JProperty("Name", "feifei"), new JProperty("Age", 30), new JProperty("Department", "chanpin"))));
jArray.Add(staff);
jArray.Add(staff);
jArray.Add(staff);
staff.Add(new JProperty("Array", jArray));
jArray = new JArray();
jArray.Add(new JValue("1"));
jArray.Add(new JValue("2"));
jArray.Add(new JValue("3"));
jArray.Add(new JValue("4"));
staff.Add(new JProperty("arr", jArray));
txtData.Text = staff.ToString();
我们还可以通过如字符串获取Json 对象,具体不演示了,看如下表格
| 方法 | 说明 |
JObject.Parse(string json) |
json含有JSON对象的字符串,返回为JObject对象,最外层是对象 |
JObject.FromObject(object o) |
o为要转化的对象,返回一个JObject对象 |
JObject.Load(JsonReader reader) |
reader包含着JSON对象的内容,返回一个JObject对象 |
JArray.Parse(string json) |
json含有JSON数组的字符串,返回为JArray对象,最外层是数组 |
JArray.FromObject(object o) |
o为要转化的对象,返回一个JArray对象 |
JArray.Load(JsonReader reader) |
reader包含着JSON对象的内容,返回一个JArray对象 |
现有如下Json
{\"Name\":\"wangbaicheng\",\"Age\":33,\"Department\":\"销售部\",\"Leader\":{\"Name\":\"feifei\",\"Age\":30,\"Department\":\"chanpin\"},\"Array\":[{\"Name\":\"wangbaicheng\",\"Age\":33,\"Department\":\"销售部\",\"Leader\":{\"Name\":\"feifei\",\"Age\":30,\"Department\":\"chanpin\"}},{\"Name\":\"wangbaicheng\",\"Age\":33,\"Department\":\"销售部\",\"Leader\":{\"Name\":\"feifei\",\"Age\":30,\"Department\":\"chanpin\"}},{\"Name\":\"wangbaicheng\",\"Age\":33,\"Department\":\"销售部\",\"Leader\":{\"Name\":\"feifei\",\"Age\":30,\"Department\":\"chanpin\"}}],\"arr\":[\"1\",\"2\",\"3\",\"4\"]}
我们来看下使用linq 和使用对象来解析Json
//对象解析json和linq 解析json
{
string json = "{\"Name\":\"wangbaicheng\",\"Age\":33,\"Department\":\"销售部\",\"Leader\":{\"Name\":\"feifei\",\"Age\":30,\"Department\":\"chanpin\"},\"Array\":[{\"Name\":\"wangbaicheng\",\"Age\":33,\"Department\":\"销售部\",\"Leader\":{\"Name\":\"feifei\",\"Age\":30,\"Department\":\"chanpin\"}},{\"Name\":\"wangbaicheng\",\"Age\":33,\"Department\":\"销售部\",\"Leader\":{\"Name\":\"feifei\",\"Age\":30,\"Department\":\"chanpin\"}},{\"Name\":\"wangbaicheng\",\"Age\":33,\"Department\":\"销售部\",\"Leader\":{\"Name\":\"feifei\",\"Age\":30,\"Department\":\"chanpin\"}}],\"arr\":[\"1\",\"2\",\"3\",\"4\"]}";
JObject jObject = JObject.Parse(json);
//使用对象解析
string name= jObject["Name"].ToString();//注意大小写
name = jObject.Value<string>("Name");//注意大小写
JArray array= jObject.Value<JArray>("Array");
for (int i = 0; i < array.Count; i++)
{
JToken token= array[i];
JObject obj = token as JObject;
Console.WriteLine("对象解析"+obj.Value<string>("Name"));
}
//linq 解析
var names = from staff in jObject["Array"].Children()
select (string)staff["Name"];
foreach (var n in names)
Console.WriteLine("linq解析"+n);
}
这里就不过多的深入了,毕竟我们大多数的时候还是比较喜欢json 转换成为我们自己定义的对象的。
四、封装自己的JsonHelper 类
public static class JsonHelper
{
#region System.Runtime.Serialization net 4.0
/// <summary>
/// 对象序列化为JSON
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="_t"></param>
/// <returns></returns>
public static string ObjectToJson<T>(this T _t)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
MemoryStream ms = new MemoryStream();
ser.WriteObject(ms, _t);
string jsonString = Encoding.UTF8.GetString(ms.ToArray());
ms.Close();
return jsonString;
}
/// <summary>
/// 对象反序列化
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="jsonString"></param>
/// <returns></returns>
public static T JsonToObject<T>(this string jsonString)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonString));
T obj = (T)ser.ReadObject(ms);
return obj;
}
#endregion
#region net3.0 System.Web.Extensions.dll
/// <summary>
/// 使用net 3.0 json 反序列化,支持json字符串属性不带引号
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="jsonString"></param>
/// <returns></returns>
public static T JsonToObj<T>(this string jsonString)
{
//引用 System.Web.Extensions.dll
System.Web.Script.Serialization.JavaScriptSerializer jss = new System.Web.Script.Serialization.JavaScriptSerializer();
return jss.Deserialize<T>(jsonString);
}
/// <summary>
/// 使用net 3.5 对象序列化成为json 反序列化,支持json字符串属性不带引号
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="jsonString"></param>
/// <returns></returns>
public static string ObjToJson<T>(this T t)
{
//引用 System.Web.Extensions.dll
System.Web.Script.Serialization.JavaScriptSerializer jss = new System.Web.Script.Serialization.JavaScriptSerializer();
return jss.Serialize(t);
}
#endregion
/// <summary>
/// JsonConvert.SerializeObject
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
public static string ToJson<T>(this T obj)
{
return JsonConvert.SerializeObject(obj);
}
/// <summary>
/// JsonConvert.DeserializeObject
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="content"></param>
/// <returns></returns>
public static T ToObject<T>(this string content)
{
return JsonConvert.DeserializeObject<T>(content);
}
}
五、Json 深入
我们都知道,在js中json的key 是可以使用数字的,我们学习了三种序列化方式,如何把一个json 的key 为数字转换成为对象,我们很多开发人员可能都会遇到过。
js 代码如下
<script type="text/javascript">
var obj={1:123,2:456};
alert("obj[1]="+obj[1]);
var objText={"1":123,"2":456};
alert("objText[1]="+objText[1]);
alert("objText[1]="+objText["2"]);
</script>
在微软,很早之前就想到了这一解决方案,但是在4.0升级的时候,升级出来一个bug ,DataContractJsonSerializer 不再支持key 为数字转换,但是其他的两种方式都支持
我们先来看下 JavaScriptSerializer 是如何序列化和反序列化key 为数字的json 。
1.JavaScriptSerializer 类序列化和反序列化 key 为数字:
Dictionary<int, string> dictionary1 = DataFactory.GetSkillAll();
Dictionary<string, string> dictionary2 = DataFactory.GetStrongAll();
try
{
string json = dictionary1.ObjToJson();
Dictionary<int, string> d = json.JsonToObj<Dictionary<int, string>>();
}
catch
{
}
try
{
string json = dictionary2.ObjToJson();
Dictionary<string, string> d = json.JsonToObj<Dictionary<string, string>>();
}
catch
{
}
我们上述代码分别为两个字典,字典值均为数字,只是一个是字符串类型的,一个是整数类型的,整数类型的无法序列化和反序列化,字符串类型的可以序列化和反序列化。
看看序列化结果。
{"1":"abcdefg","2":"abcdefg123","3":"tvbcd","4":"您吃了吗","5":"吃了吗您呢","6":"tvbc 早上好","7":"vbbc 晚上好"}
也就是说,微软在早期是只支持字符串类型的数字key
2.JsonConvert类序列化和反序列化:
这个放心,不论key 是int 类型还是string 类型,都支持的,我们看下案列
Dictionary<int, string> dictionary1 = DataFactory.GetSkillAll();
Dictionary<string, string> dictionary2 = DataFactory.GetStrongAll();
string json1= dictionary1.ToJson();
string json2=dictionary2.ToJson();
Console.WriteLine(json1);
Console.WriteLine(json2);
完
源码下载地址: 点击下载
面向对象六大原则
这是设计模式系列开篇的第一篇文章。也是我学习设计模式过程中的总结。这篇文章主要讲的是面向对象设计中,我们应该遵循的六大原则。只有掌握了这些原则,我们才能更好的理解设计模式。
我们接下来要介绍以下6个内容。
- 单一职责原则——SRP
- 开闭原则——OCP
- 里式替换原则——LSP
- 依赖倒置原则——DIP
- 接口隔离原则——ISP
- 迪米特原则——LOD
单一职责原则
单一职责原则的定义是就一个类而言,应该仅有一个引起他变化的原因。也就是说一个类应该只负责一件事情。如果一个类负责了方法M1,方法M2两个不同的事情,当M1方法发生变化的时候,我们需要修改这个类的M1方法,但是这个时候就有可能导致M2方法不能工作。这个不是我们期待的,但是由于这种设计却很有可能发生。所以这个时候,我们需要把M1方法,M2方法单独分离成两个类。让每个类只专心处理自己的方法。
单一职责原则的好处如下:
- 可以降低类的复杂度,一个类只负责一项职责,这样逻辑也简单很多
- 提高类的可读性,和系统的维护性,因为不会有其他奇怪的方法来干扰我们理解这个类的含义
- 当发生变化的时候,能将变化的影响降到最小,因为只会在这个类中做出修改。
开闭原则
开闭原则和单一职责原则一样,是非常基础而且一般是常识的原则。开闭原则的定义是软件中的对象(类,模块,函数等)应该对于扩展是开放的,但是对于修改是关闭的。
当需求发生改变的时候,我们需要对代码进行修改,这个时候我们应该尽量去扩展原来的代码,而不是去修改原来的代码,因为这样可能会引起更多的问题。
这个准则和单一职责原则一样,是一个大家都这样去认为但是又没规定具体该如何去做的一种原则。
开闭原则我们可以用一种方式来确保他,我们用抽象去构建框架,用实现扩展细节。这样当发生修改的时候,我们就直接用抽象了派生一个具体类去实现修改。
里氏替换原则
里氏替换原则是一个非常有用的一个概念。他的定义
如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有对象o1都替换成o2的时候,程序P的行为都没有发生变化,那么类型T2是类型T1的子类型。
这样说有点复杂,其实有一个简单的定义
所有引用基类的地方必须能够透明地使用其子类的对象。
里氏替换原则通俗的去讲就是:子类可以去扩展父类的功能,但是不能改变父类原有的功能。他包含以下几层意思:
- 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
- 子类可以增加自己独有的方法。
- 当子类的方法重载父类的方法时候,方法的形参要比父类的方法的输入参数更加宽松。
- 当子类的方法实现父类的抽象方法时,方法的返回值要比父类更严格。
里氏替换原则之所以这样要求是因为继承有很多缺点,他虽然是复用代码的一种方法,但同时继承在一定程度上违反了封装。父类的属性和方法对子类都是透明的,子类可以随意修改父类的成员。这也导致了,如果需求变更,子类对父类的方法进行一些复写的时候,其他的子类无法正常工作。所以里氏替换法则被提出来。
确保程序遵循里氏替换原则可以要求我们的程序建立抽象,通过抽象去建立规范,然后用实现去扩展细节,这个是不是很耳熟,对,里氏替换原则和开闭原则往往是相互依存的。
依赖倒置原则
依赖倒置原则指的是一种特殊的解耦方式,使得高层次的模块不应该依赖于低层次的模块的实现细节的目的,依赖模块被颠倒了。
这也是一个让人难懂的定义,他可以简单来说就是
高层模块不应该依赖底层模块,两者都应该依赖其抽象
抽象不应该依赖细节
细节应该依赖抽象
在Java 中抽象指的是接口或者抽象类,两者皆不能实例化。而细节就是实现类,也就是实现了接口或者继承了抽象类的类。他是可以被实例化的。高层模块指的是调用端,底层模块是具体的实现类。在Java中,依赖倒置原则是指模块间的依赖是通过抽象来发生的,实现类之间不发生直接的依赖关系,其依赖关系是通过接口是来实现的。这就是俗称的面向接口编程。
我们下面有一个例子来讲述这个问题。这个例子是工人用锤子来修理东西。我们的代码如下:
public class Hammer {
public String function(){
return "用锤子修理东西";
}
}
public class Worker {
public void fix(Hammer hammer){
System.out.println("工人" + hammer.function());
}
public static void main(String[] args) {
new Worker().fix(new Hammer());
}
}
这个是一个很简单的例子,但是如果我们要新增加一个功能,工人用 螺丝刀来修理东西,在这个类,我们发现是很难做的。因为我们Worker类依赖于一个具体的实现类Hammer。所以我们用到面向接口编程的思想,改成如下的代码:
public interface Tools {
public String function();
}
然后我们的Worker是通过这个接口来于其他细节类进行依赖。代码如下:
public class Worker {
public void fix(Tools tool){
System.out.println("工人" + tool.function());
}
public static void main(String[] args) {
new Worker().fix(new Hammer());
new Worker().fix(new Screwdriver());
}
}
我们的Hammer类与Screwdriver类实现这个接口
public class Hammer implements Tools{
public String function(){
return "用锤子修理东西";
}
}
public class Screwdriver implements Tools{
@Override
public String function() {
return "用螺丝刀修理东西";
}
}
这样,通过面向接口编程,我们的代码就有了很高的扩展性,降低了代码之间的耦合度,提高了系统的稳定性。
接口隔离原则
接口隔离原则的定义是
客户端不应该依赖他不需要的接口
换一种说法就是类间的依赖关系应该建立在最小的接口上。这样说好像更难懂。我们通过一个例子来说明。我们知道在Java中一个具体类实现了一个接口,那必然就要实现接口中的所有方法。如果我们有一个类A和类B通过接口I来依赖,类B是对类A依赖的实现,这个接口I有5个方法。但是类A与类B只通过方法1,2,3依赖,然后类C与类D通过接口I来依赖,类D是对类C依赖的实现但是他们却是通过方法1,4,5依赖。那么是必在实现接口的时候,类B就要有实现他不需要的方法4和方法5 而类D就要实现他不需要的方法2,和方法3。这简直就是一个灾难的设计。
所以我们需要对接口进行拆分,就是把接口分成满足依赖关系的最小接口,类B与类D不需要去实现与他们无关接口方法。比如在这个例子中,我们可以把接口拆成3个,第一个是仅仅由方法1的接口,第二个接口是包含2,3方法的,第三个接口是包含4,5方法的。
这样,我们的设计就满足了接口隔离原则。
以上这些设计思想用英文的第一个字母可以组成SOLID ,满足这个5个原则的程序也被称为满足了SOLID准则。
迪米特原则
迪米特原则也被称为最小知识原则,他的定义
一个对象应该对其他对象保持最小的了解。
因为类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大,所以这也是我们提倡的软件编程的总的原则:低耦合,高内聚。
迪米特法则还有一个更简单的定义
只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。
这里我们可以用一个现实生活中的例子来讲解一下。比如我们需要一张CD,我们可能去音像店去问老板有没有我们需要的那张CD,老板说现在没有,等有的时候你们来拿就行了。在这里我们不需要关心老板是从哪里,怎么获得的那张CD,我们只和老板(直接朋友)沟通,至于老板从他的朋友那里通过何种条件得到的CD,我们不关心,我们不和老板的朋友(陌生人)进行通信,这个就是迪米特的一个应用。说白了,就是一种中介的方式。我们通过老板这个中介来和真正提供CD的人发生联系。
总结
到这里,面向对象的六大原则,就写完了。我们看出来,这些原则其实都是应对不断改变的需求。每当需求变化的时候,我们利用这些原则来使我们的代码改动量最小,而且所造成的影响也是最小的。但是我们在看这些原则的时候,我们会发现很多原则并没有提供一种公式化的结论,而即使提供了公式化的结论的原则也只是建议去这样做。这是因为,这些设计原则本来就是从很多实际的代码中提取出来的,他是一个经验化的结论。怎么去用它,用好他,就要依靠设计者的经验。否则一味者去使用设计原则可能会使代码出现过度设计的情况。大多数的原则都是通过提取出抽象和接口来实现,如果发生过度的设计,就会出现很多抽象类和接口,增加了系统的复杂度。让本来很小的项目变得很庞大,当然这也是Java的特性(任何的小项目都会做成中型的项目)
原文:
http://www.cnblogs.com/qifengshi/p/5709594.html
(第一篇) 一步一步带你了解linq to Object
要想学好linq to object 我们必须要先学习lambda 表达式,学习lambda 表达式呢我们必须了解匿名函数和匿名类及扩展方法,学习匿名函数,我们必须学会委托,这是本文的宗旨。下面开始第一步。在第一步开始之前,我们做点准备工作,建立一个学生类和一个班级类,类结构如下
public class Student
{
public int Id { get; set; }
public int ClassId { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public void Study()
{
Console.WriteLine("{0} {1}跟着Eleven老师学习.net高级开发", this.Id, this.Name);
}
}
public class Class
{
public int Id { get; set; }
public string ClassName { get; set; }
}
在准备一个基础数据类
public class LinqShow
{
/// <summary>
/// 准备数据
/// </summary>
/// <returns></returns>
public List<Student> GetStudentList()
{
#region 初始化数据
List<Student> studentList = new List<Student>()
{
new Student()
{
Id=1,
Name="打兔子的猎人",
ClassId=2,
Age=35
},
new Student()
{
Id=1,
Name="Alpha Go",
ClassId=2,
Age=23
},
new Student()
{
Id=1,
Name="白开水",
ClassId=2,
Age=27
},
new Student()
{
Id=1,
Name="狼牙道",
ClassId=2,
Age=26
},
new Student()
{
Id=1,
Name="Nine",
ClassId=2,
Age=25
},
new Student()
{
Id=1,
Name="Y",
ClassId=2,
Age=24
},
new Student()
{
Id=1,
Name="小昶",
ClassId=2,
Age=21
},
new Student()
{
Id=1,
Name="yoyo",
ClassId=2,
Age=22
},
new Student()
{
Id=1,
Name="冰亮",
ClassId=2,
Age=34
},
new Student()
{
Id=1,
Name="瀚",
ClassId=2,
Age=30
},
new Student()
{
Id=1,
Name="毕帆",
ClassId=2,
Age=30
},
new Student()
{
Id=1,
Name="一点半",
ClassId=2,
Age=30
},
new Student()
{
Id=1,
Name="小石头",
ClassId=2,
Age=28
},
new Student()
{
Id=1,
Name="大海",
ClassId=2,
Age=30
},
new Student()
{
Id=3,
Name="yoyo",
ClassId=3,
Age=30
},
new Student()
{
Id=4,
Name="unknown",
ClassId=4,
Age=30
}
};
#endregion
return studentList;
}
}
简单了解委托
1. 委托是什么?
其实,我一直思考如何讲解委托,才能把委托说得更透彻。说实话,每个人都委托都有不同的见解,因为看问题的角度不同。个人认为,可以从以下2点来理解:
(1) 从数据结构来讲,委托是和类一样是一种用户自定义类型。
(2) 从设计模式来讲,委托(类)提供了方法(对象)的抽象。
既然委托是一种类型,那么它存储的是什么数据?
我们知道,委托是方法的抽象,它存储的就是一系列具有相同签名和返回回类型的方法的地址。调用委托的时候,委托包含的所有方法将被执行。
2. 委托类型的定义
委托是类型,就好像类是类型一样。与类一样,委托类型必须在被用来创建变量以及类型对象之前声明。
delegate void MyDel(int x);
委托类型声明:
(1) 以deleagate关键字开头。
(2)返回类型+委托类型名+参数列表。
3. 声明委托变量
MyDel del1,del2;
4. 初始化委托变量
(1) 使用new运算符
new运算符的操作数的组成如下:
- 委托类型名
- 一组圆括号,其中包含作为调用列表中的第一个成员的方法的名字。方法可以是实例方法或静态方法。
del1 = new MyDel( 实例方法名 ); del2 = new MyDel( 静态方法名 );
(2)使用快捷语法
快键语法,它仅由方法说明符构成。之所以能这样,是因为在方法名称和其相应的委托类型之间有隐式转换。
del1 = 实例方法名; del2 = 静态方法名;
5. 赋值委托
由于委托是引用类型,我们可以通过给它赋值来改变包含在委托变量中的方法地址引用。旧的引用会被垃圾回收器回收。
MyDel del; del = 实例方法名; //委托初始化 del = 静态方法名;//委托重新赋值,旧的引用将被回收
6. 委托调用
委托调用跟方法调用类似。委托调用后,调用列表的每个方法将会被执行。
在调用委托前,应判断委托是否为空。调用空委托会抛出异常,这是正常标准调用
if(null != del)
{
del.Invoke();//委托调用
}
在net 1.1的时代,我们是使用Invoke()方法类调用委托的。但是微软在net 2.0 的时候,我们可以这么去调用委托 直接 del();
if(null != del)
{
del();//委托调用
}
7. 综合练习
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Test
{
class Program
{
public delegate void meathed1();//1 委托的声明 委托是一种类型
public delegate int meathed2();//2 可以声明在类的内部,也可以声明在类的外部
public delegate void meathed3(int id, string name);
public delegate string meathed4(string msg);
static void Main(string[] args)
{
meathed1 me1 = new meathed1(m1);
meathed2 me2 = new meathed2(m2);
meathed3 me3 = new meathed3(m3);
meathed4 me4 = new meathed4(m4);
me1.Invoke();
int result= me2.Invoke();
Console.WriteLine(result);
me3(1, "wbc");
Console.WriteLine(me4("您吃了吗"));
Console.Read();
}
public static void m1() {
Console.WriteLine("我是无参数无返回值的方法");
}
public static int m2()
{
Console.WriteLine("我是无参数有返回值的方法~~~~~~~~~");
return 123;
}
public static void m3(int id, string name)
{
Console.WriteLine($"我是有参数无返回值的方法,id={id},name={name}");
}
public static string m4(string sparams)
{
Console.WriteLine($"我是无参数无返回值的方法 ,参数{sparams}");
return "参数返回了";
}
}
}
8. 多播委托
多播委托(MulticastDelegate)继承自 Delegate ,表示多路广播委托;即,其调用列表中可以拥有多个元素的委托。实际上,我们自定义的委托的基类就是 MulticastDelegate。这句话不好理解,继续往下看

当我们自己写一个类去继承Delegate 的时候,开发工具提示我 特殊类型不允许被继承,估计这是net 唯一一个特殊类型。
public abstract class myted : Delegate {
}
我们来看一个案列来使用下多播委托
meathed1 me1 = new meathed1(m1);
me1 += m2;
me1 += m3;
me1.Invoke();
me1 -= m2;
me1();
多播委托:
+= 的时候就是在委托的实例之后加入注册更多的方法,像一个链子,执行的时候按加入的先后顺序执行
-=的时候就是在委托的实例之后移除注册更多的方法。从链子的尾部开始匹配,找到一个完全吻合的移除,没有找到不会触发异常。
注:实例方法因为每一次实例地址的引用不一致,所以无法移除。
9. net 框架提供的自带委托
8.1.Func委托类型
Func是有返回值的泛型委托,可以没有参数,但最多只有16个参数,并且必须要有返回值。
Func<string, int> funcDelegate = new Func<string, int>();
2.Action委托类型
Action是没有返回值的泛型委托,可以没有参数,但最多只有16个参数。
Action<string, string> method = new Action<string, string>();
关于泛型这里就不过多介绍了,本人前边写了一篇比较详细。建议大家以后使用委托的时候,尽量使用net 类库自带的委托。
这里扩展下事件的基本使用,事件就是在委托的实例加上一个event 的关键字。所以委托是一种类型,事件就是委托类型的实例。这里只是扩展,不懂不影响学习linq to object
public void Meta() {
}
public delegate void meathed1();
public class Cat {
public event meathed1 met_event;
public meathed1 mea = new meathed1(m1);
public void Run() {
mea += m2;
mea.Invoke();
met_event = new meathed1(m1);
met_event += m2;
met_event.Invoke();
}
public static void m1() {
}
public static void m2() {
}
}
调用方法如下
Cat c = new Cat();
c.mea = Meta;
c.mea.Invoke();
// c.met_event = meta;这种写法是不允许的,只有通过订阅的方式才允许,如下
c.met_event += new meathed1(Meta);
使用事件和委托的区别我们一下子就能区分出来:事件更安全,在类的外部不允许破坏他的原有执行内容,只有在从新订阅 event关键字限制了权限,保证安全
匿名方法&lambda表达式
我们看了上面的委托,发现写起来好麻烦,并且用起来特别的不方便,有没有更简便的方法呢?可不可以把我们的方法写到委托里面呢?答案是肯定的;这种写法我们称为匿名方法。写法如下:
Action<string> method = new Action<string>(delegate(string name){
Console.WriteLine(name);
});
method.Invoke("wbc");
Console.Read();
但是这种写法还不够简便,在.net 2.0的时候,我们发现1.1的时候在频繁的使用委托,微软做了很大的改进和更新,新的写法如下
Action<string> method = new Action<string>(name =>
Console.WriteLine(name)
);
method.Invoke("wbc");
Console.Read();
在2.0里面,我们去掉了delegate关键字,如果子有一个参数的时候,连括号也省略了,用"=>"指向方法体,这个尖叫号等号我们读做“goes to”,如果方法只有一句话,连花括号也省略了。参数类型直接就可以推算出来。看一个复杂的案列
Func<string,int,string> method = new Func<string, int, string>((name,age) => {
return $"{name}今年{age}岁了";
}
);
string result= method.Invoke("wbc",0);
Console.WriteLine(result);
Console.Read();
其实我们的匿名方法就是lambda 表达式。
匿名类
有了匿名方法一定会有匿名类,在net 3.0的时候才有的匿名类,我们看下在没有匿名类的时候,我们是如何去写的。
object student = new {
id=123,
name="1234"
};
Console.WriteLine(student);
Console.Read();
这种写法只能输出 “{ id = 123, name = 1234 }”这种结果的Json 数据,并且我们在程序中不能使用student.id,会报错。看下匿名写法
var model = new
{
Id = 1,
Name = "Alpha Go",
Age = 21,
ClassId = 2
};
Console.WriteLine(model.Age);
Console.Read();
我们来看下var 关键字,其实var 关键字就是一个语法糖,我们不必过于在意,这里就不过多的演示了,我们的根本目的是linq to object
1. 必须在定义时初始化。也就是必须是var s = “abcd”形式,而不能是如下形式:
var s;
s = “abcd”;//这种写法是不允许的
2. 一但初始化完成,就不能再给变量赋与初始化值类型不同的值了。
3. var要求是局部变量。
4. 使用var定义变量和object不同,它在效率上和使用强类型方式定义变量完全一样。
这里在简要扩展点技术点“dynamic”,dynamic的用法和object差不多,区别是dynamic在编译的时候才声明的一个类型,但是本人建议在程序开发中尽量避开这个关键字,后续MVC 中在详细介绍,dynamic是net 40里面的关键字。
扩展方法
扩展方法必须建立在静态类中的静态方法,且第一个参数前边要加上this 关键字,使用扩展方法必须引用扩展方法所在的类的命名空间,如果想要全局使用,可以不带命名空间。个人建议程序中的扩展方法尽量要带上命名空间。我们来看下扩展方法的使用
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Test
{
class Program
{
static void Main(string[] args)
{
int? a = null;
Console.WriteLine(Helper.ToInt(a));//常规写法输出
Console.WriteLine(a.ToInt());//扩展方法输出
if ("1qazwsx1qazwsx1qazwsx1qazwsx".ToLengthInCount(16))
{
Console.WriteLine("密码长度应当小于16个字符");
}
Console.Read();
}
}
public static class Helper
{
/// <summary>
/// 验证一个数字是否为空,为空返回-1
/// 扩展方法:静态类里的静态方法,第一个参数类型前面加上this
/// </summary>
/// <param name="iParameter"></param>
/// <returns></returns>
public static int ToInt(this int? iParameter) {
return iParameter ?? -1;
}
/// <summary>
/// 验证一个字符串长度是否大指定长度
///
/// </summary>
/// <param name="sParameter"></param>
/// <returns></returns>
public static bool ToLengthInCount(this string sParameter, int iCount)
{
return sParameter.Length >= iCount ? true : false;
}
}
}
这里简要说明下,扩展方法的方法名和类内不得方法名相同,会优先选择类内部的方法,这是以就近原则,去执行的,关于就近原则,请看本人java 相关博客,有介绍
linq to object
1. 什么是linq to object ?
LINQ即Language Integrated Query(语言集成查询),LINQ是集成到C#和Visual Basic.NET这些语言中用于提供查询数据能力的一个新特性。linq to object 就是对象查询体系。
注:LINQ(发音为Link)
我们来看一个简单的列子,用到了我们前边准备的两个类。我们要取出学生年龄小于30岁的,我们如果不会linq to object 的写法如下
LinqShow linq = new LinqShow();
List<Student> studentList = linq.GetStudentList();//获取基础数据
List<Student> studentListLessThan30 = new List<Student>();
foreach (var item in studentList)
{
if (item.Age < 30)
{
Console.WriteLine($"name={item.Name},age={item.Age}");
studentListLessThan30.Add(item);
}
}
Console.Read();
我们可不可以使用委托去计算呢??
LinqShow linq = new LinqShow();
List<Student> studentList = linq.GetStudentList();//获取基础数据
List<Student> studentListLessThan30 = new List<Student>();
//Func<Student, bool> predicate = s => s.Age < 30;//写法一,
//var list = studentList.Where<Student>(predicate);//linq
var list = Enumerable.Where(studentList, s => s.Age < 30);//使用lambda 表达式写法
foreach (var item in list)
{
Console.WriteLine($"Name={item.Name} Age={item.Age}");
}
Console.Read();
两段代码我就不比较了,这里我们只来剖析下 studentList 的Where方法,我们可以转移到定义看下,其实where 里面的参数,如下图

我们会发现,这不List<T>自带的一个方法,而是一个扩展方法,这个扩展方法在”System.Linq“命名空间的“public static class Enumerable”类下,也就是说根据扩展方法的特点,我们只要引用了“System.Linq”命名空间,就能使用这个扩展方法,这个扩展方法真的第二个参数是一个Func<TSource, bool> 委托,这个委托有一个TSource泛型参数,一个bool返回值。这就说明了我们上面的lambda 表达式是成立的。
继续剖析,我们接受的时候,没有使用List<Student>接收返回的集合,这是因为我们的linq 只有遇到循环的时候或遇到ToList<T>()的时候才会去迭代,这里涉及到迭代器的问题,后续我们会详细讲解迭代器的使用,或者大家去网上查询下设计模式迭代器。这么说,我们很难理解。我们通过一个案列来分析,自己写一个for 循环的扩展方法,对比下
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Test
{
class Program
{
static void Main(string[] args)
{
LinqShow linq = new LinqShow();
List<Student> studentList = linq.GetStudentList();//获取基础数据
//var list = Enumerable.Where(studentList, s => s.Age < 30);//使用lambda 表达式写法,
Console.WriteLine($"-------------------------linq写法-----------------------------------");
var list = studentList.Where( s => {
Console.WriteLine($"我的年龄是{s.Age}");
return s.Age < 30;
});//使用lambda 表达式写法,这里在每一次计算,我们都输出一个我的年龄是,这里不再使用 Enumerable 去调用where ,反正是扩展方法,用法是一样的
foreach (var item in list)
{
Console.WriteLine($"Name={item.Name} Age={item.Age}");
}
Console.WriteLine($"-------------------------自定义扩展方法写法-----------------------------------");
var Wbclist = studentList.WbcWhere(s => {
Console.WriteLine($"Wbc我的年龄是{s.Age}");
return s.Age < 30;
});// 自定义扩展方法写法 这里不再使用 Enumerable 去调用where
foreach (var item in Wbclist)
{
Console.WriteLine($"Name={item.Name} Age={item.Age}");
}
Console.Read();
}
}
public static class Helper
{
/// <summary>
/// 自定义扩展方法计算集合
/// </summary>
/// <typeparam name="TSource"></typeparam>
/// <param name="source"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static IEnumerable<TSource> WbcWhere<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
//常规情况下数据过滤
List<TSource> studentListLessThan30 = new List<TSource>();
foreach (var student in source)
{
//if (item.Age < 30)
if (predicate(student))
{
studentListLessThan30.Add(student);
}
}
return studentListLessThan30;
}
/// <summary>
/// 验证一个数字是否为空,为空返回-1
/// 扩展方法:静态类里的静态方法,第一个参数类型前面加上this
/// </summary>
/// <param name="iParameter"></param>
/// <returns></returns>
public static int ToInt(this int? iParameter) {
return iParameter ?? -1;
}
/// <summary>
/// 验证一个字符串长度是否大指定长度
///
/// </summary>
/// <param name="sParameter"></param>
/// <returns></returns>
public static bool ToLengthInCount(this string sParameter, int iCount)
{
return sParameter.Length >= iCount ? true : false;
}
}
}
我们来看下输入结果


通过执行结果,我们会发现,自定义的扩展方法是循环一遍,统一输出的,没有延迟,而linq 的写法有延迟。这也就是我们linq 核心之一 “linq 是有延迟的”。这里特别说明,linq 的延迟加载和EF ,ORM 的延迟加载,不是一会事,这里说的延迟,指的是当用到某条数据的时候,才会去查询。
2. 高逼格装X时刻
我们以llambda表达式去写linq 称为 查询运算符写法,下边我们 使用 查询表达式去写,注意区分什么是查询表达式,什么是查询运算符写法,我面试的很多人都说反了。
案列一 多条件过滤筛选数据
LinqShow linq = new LinqShow();
List<Student> studentList = linq.GetStudentList();//获取基础数据
//查询运算符
var list1 = studentList.Where<Student>(s => s.Age < 30 && s.Name.Length > 3);//陈述句
foreach (var item in list1)
{
Console.WriteLine("Name={0} Age={1}", item.Name, item.Age);
}
//查询表达式
Console.WriteLine("********************");
var list2 = from s in studentList
where s.Age < 30 && s.Name.Length > 3
select s;
foreach (var item in list2)
{
Console.WriteLine("Name={0} Age={1}", item.Name, item.Age);
}
Console.Read();
我们会发现,表达式的写法是不是很像sql ,语法以from 对象 ... in .. where select 对象 ,是不是很高大上,够逼咯吧!!!!!!,下边在简单介绍几个案列
案列二将筛选数据付给匿名类或者实体类
{
Console.WriteLine("********************");
var list = studentList.Where<Student>(s => s.Age < 30)
.Select(s => new
{
IdName = s.Id + s.Name,
ClassName = s.ClassId == 2 ? "高级班" : "其他班"
});
foreach (var item in list)
{
Console.WriteLine("Name={0} Age={1}", item.ClassName, item.IdName);
}
}
{
Console.WriteLine("********************");
var list = from s in studentList
where s.Age < 30
select new
{
IdName = s.Id + s.Name,
ClassName = s.ClassId == 2 ? "高级班" : "其他班"
};
foreach (var item in list)
{
Console.WriteLine("Name={0} Age={1}", item.ClassName, item.IdName);
}
}
案列三 排序分页
var list = studentList.Where<Student>(s => s.Age < 30)
.Select(s => new
{
Id = s.Id,
ClassId = s.ClassId,
IdName = s.Id + s.Name,
ClassName = s.ClassId == 2 ? "高级班" : "其他班"
})
.OrderBy(s => s.Id)//首先升序排序
.OrderByDescending(s => s.ClassId)//首先降序排序
.ThenBy(s => s.ClassId)//升序降序排序以后再排序
.Skip(2)//跳过 分页
.Take(3)//获取数据
;
foreach (var item in list)
{
Console.WriteLine($"Name={item.ClassName} Age={item.IdName}");
}
案列四 关联查询
首先添加班级数据
List<Class> classList = new List<Class>()
{
new Class()
{
Id=1,
ClassName="初级班"
},
new Class()
{
Id=2,
ClassName="高级班"
},
new Class()
{
Id=3,
ClassName="微信小程序"
},
};
表达式写法
var list = from s in studentList
join c in classList on s.ClassId equals c.Id
select new
{
Name = s.Name,
CalssName = c.ClassName
};
foreach (var item in list)
{
Console.WriteLine($"Name={item.Name},CalssName={item.CalssName}");
}
算数运输符写法
var list = studentList.Join(classList, s => s.ClassId, c => c.Id, (s, c) => new
{
Name = s.Name,
CalssName = c.ClassName
});
foreach (var item in list)
{
Console.WriteLine($"Name={item.Name},CalssName={item.CalssName}");
}
在linq 中没有右链接,如果要写右链接,只能反过来写,这里不过多去写案列了,这种案列网上很多,一会总结过后,分享几篇
总结及推荐
1.匿名函数只能使用在委托中
2.var 关键字的特点,如何使用var 实现匿名类。
3. lambda 表达式
4.扩展方法
5.运算符linq 写法
6.表达式linq 写法
7 linq 的延迟加载原理
分享 1 linq 案列 ,很全面的
分享2 linq 标准查询操作符 很全面的 共五篇
10分钟浅谈泛型协变与逆变
首先声明,本文写的有点粗糙,只让你了解什么是协变和逆变,没有深入研究,根据这些年的工作经验,发现我们在开发过程中,很少会自己去写逆变和协变,因为自从net 4.0 (Framework 3.0) 以后,.net 就为我们提供了 定义好的逆变与协变。我们只要会使用就可以。协变和逆变都是在泛型中使用的。
-
什么是逆变与协变呢
可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用。如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量。协变和逆变是两个相互对立的概念:
- 如果某个返回的类型可以由其派生类型替换,那么这个类型就是支持协变的
- 如果某个参数类型可以由其基类替换,那么这个类型就是支持逆变的。
看起来你有点绕,我们先准备个“”鸟”类,在准备一个“麻雀”类,让麻雀继承鸟类,一起看代码研究
/// <summary>
/// 鸟
/// </summary>
public class Bird
{
public int Id { get; set; }
}
/// <summary>
/// 麻雀
/// </summary>
public class Sparrow : Bird
{
public string Name { get; set; }
}
我们分别取实例化这个类,发现程序是能编译通过的。
Bird bird1 = new Bird();
Bird bird2 = new Sparrow();
Sparrow sparrow1 = new Sparrow();
//Sparrow sparrow2 = new Bird();//这个是编译不通过的,违反了继承性。
但是我们放在集合中,去实例化,是无法通过的
List<Bird> birdList1 = new List<Bird>();
//List<Bird> birdList2 = new List<Sparrow>();//不是父子关系,没有继承关系
//一群麻雀一定是一群鸟
那么我们如何去实现在泛型中的继承性呢??这就引入了协变和逆变得概念,为了保证类型的安全,C#编译器对使用了 out 和 in 关键字的泛型参数添加了一些限制:
- 支持协变(
out)的类型参数只能用在输出位置:函数返回值、属性的get访问器以及委托参数的某些位置 - 支持逆变(
in)的类型参数只能用在输入位置:方法参数或委托参数的某些位置中出现。
-
协变
我们来看下Net “System.Collections.Generic”命名空间下的IEnumerable泛型 接口,会发现他的泛型参数使用了out

现在我们使用下 IEnumerable 接口来进行一下上述实力,会发现,我们的泛型有了继承关系。
IEnumerable<Bird> birdList1 = new List<Bird>();
IEnumerable<Bird> birdList2 = new List<Sparrow>();//协变
//一群麻雀一定是一群鸟
下面我们来自己定义一个协变泛型接口ICustomerListOut<Out T>,让 CustomerListOut 泛型类继承CustomerListOut<Out T> 泛型接口。
代码如下
/// <summary>
/// out 协变 只能是返回结果
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ICustomerListOut<out T>
{
T Get();
// void Show(T t);//T不能作为传入参数
}
/// <summary>
/// 类没有协变逆变
/// </summary>
/// <typeparam name="T"></typeparam>
public class CustomerListOut<T> : ICustomerListOut<T>
{
public T Get()
{
return default(T);
}
public void Show(T t)
{
}
}
我们会发现,在泛型斜变的时候,泛型不能作为方法的参数。我们用自己定义的泛型接口和泛型类进行实例化试试,我们会发现编译通过
ICustomerListOut<Bird> customerList1 = new CustomerListOut<Bird>();//这是能编译的
ICustomerListOut<Bird> customerList2 = new CustomerListOut<Sparrow>();//这也是能编译的,在泛型中,子类指向父类,我们称为协变
到这里协变我们就学完了,协变就是让我们的泛型有了子父级的关系。本文开始的时候,协变和逆变,是在C# 4.0 以后才有的,那C# 4.0以前我们是怎么写的呢,那个时候没有协变?
老版本的写法
List<Bird> birdList3 = new List<Sparrow>().Select(c => (Bird)c).ToList();//4.0以前的写法
等学完逆变,本文列出C# 4.0 以后的版本 中framework 已经定义好的协变、逆变 泛型接口,泛型类,泛型委托。
-
逆变
刚才我们学习了泛型参数用out 去修饰,饺子协变,现在来学习下逆变,逆变是使用in来修饰的
这里就是Net 4.0 给我们提供的逆变写法

我们自己写一个逆变的接口 ICustomerListIn<in T> ,在写一个逆变的 泛型类 CustomerListIn<T>:ICustomerListIn<T> ,代码如下
/// <summary>
/// 逆变
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ICustomerListIn<in T>
{
//T Get();//不能作为返回值
void Show(T t);
}
public class CustomerListIn<T> : ICustomerListIn<T>
{
public T Get()
{
return default(T);
}
public void Show(T t)
{
}
}
逆变的泛型参数是不能作为泛型方法的返回值的,我们来看下实例化鸟类,和麻雀类,看好使不好使。
ICustomerListIn<Sparrow> customerList2 = new CustomerListIn<Sparrow>();
ICustomerListIn<Sparrow> customerList1 = new CustomerListIn<Bird>();//父类指向子类,我们称为逆变
ICustomerListIn<Bird> birdList1 = new CustomerListIn<Bird>();
birdList1.Show(new Sparrow());
birdList1.Show(new Bird());
Action<Sparrow> act = new Action<Bird>((Bird i) => { });
到此我们就完全学完了逆变与协变
-
总结
逆变与协变只能放在泛型接口和泛型委托的泛型参数里面,
在泛型中out修饰泛型称为协变,协变(covariant) 修饰返回值 ,协变的原理是把子类指向父类的关系,拿到泛型中。
在泛型中in 修饰泛型称为逆变, 逆变(contravariant )修饰传入参数,逆变的原理是把父类指向子类的关系,拿到泛型中。
-
NET 中自带的斜变逆变泛型
| 序号 | 类别 | 名称 |
| 1 | 接口 | IEnumerable<out T> |
| 2 | 委托 | Action<in T> |
| 3 | 委托 | Func<out TResult> |
| 4 | 接口 | IReadOnlyList<out T> |
| 5 | 接口 | IReadOnlyCollection<out T> |
各位朋友,如果谁还知道,请留言告知


浙公网安备 33010602011771号