泛型,顾名思义就是指一种抽象意义的数据类型,它满足一定的类型约束条件,比如必须要有无参的构造函数,或者必须继承某些接口,等等。如果说接口是对类成员的定义与声明的话,那么泛型是对类型声明方式的声明和约束,这些抽象的数据类型可以代表符合条件的多种类型的数据,这些类型有共同的处理逻辑。
比如对于系统标准的泛型List<T>,其中T代表任意的数据类型,List规定了可以同时存放多个T类型的数据,并且这些数据是有序排列的,List规定了T的访问方式,比如说List<T>,Add(T obj)代表向List添加一个孩子元素,后添加的元素序号大于前添加的元素,List实现了一套元素访问机制,可以对加入自己的T类型孩子进行增删改查,比如List<T>.RemoveAt(int i index)代表删掉加入的第i个孩子,List可以对队列中间任意一个位置插入一个元素等等。
List<K> List<T>,Join(List<U> b,Func<T,object> a,Func<U,obj> b,Func<T,U,K> c)
代表一种泛型方法,他实现了两个List<T> ,List<U>的关联方式,参数类型Func<T,object>代表一个以T类型数据作为输入,输出object类型的方法,系统将a和b这两个参数的取值逻辑委托给调用方,让调用方确定列表T和U返回的关联值,系统判断如果这两个值相等,就把T类型的数据行和U类型的数据行关联起来,最后一个参数c,也是一个委托,它将T数据和U数据作为数据,让调用方确定需要返回什么,也就是说K类型是调用方确定的,然后系统最终返回一个List<K>,调用方完全不需要知道List<T>和List<U>是如何关联的,只需要调用方输入必要的声明信息,系统自动处理。
结合这种特性,我们可以开发自定义的泛型。
下面举一个例子,说说使用泛型比不使用泛型的优势
假设我要通过一个地址去获取服务器数据,服务器返回一串结构化文本,我通过正则表达式去匹配我需要的数据然后返回一个List列表
常规的写法:
获取基金抬头数据
public static IEnumerable<Fund> GetFund(string findstring = null)
{
string resultstr = Util.HttpCommon.HttpGet(@"http://fund.eastmoney.com/js/fundcode_search.js?v=" + DateTime.Now.ToString("yyyyMMddHHmmss"));
//Regex regex = new Regex("var r = (?<Data>\\[\\[\"(?<Code>[0-9]{6})\",\".*?\",\"(?<Name>.*?)\",\"(?<Type>.*?)\".*?\\],{0,1})*\\]");
Regex regex = new Regex("var r = (?<Data>\\[(\\[\"(?<Code>[0-9]{6})\"\\,\".*?\"\\,\"(?<Name>.*?)\"\\,\"(?<Type>.*?)\".*?\\][\\,]{0,1})+\\])");
Match match = regex.Match(resultstr);
//int matchSize = match.Groups["Code"].Captures.Count;
IEnumerable<Fund> s = match.Groups["Code"].Captures.Cast<Capture>().Select((v, i) => new { value = v.Value, index = i }).Join(match.Groups["Name"].Captures.Cast<Capture>().Select((v, i) => new { value = v.Value, index = i }), a => a.index, b => b.index, (a, b) => new { Code = a.value, Name = b.value, a.index }).Join(match.Groups["Type"].Captures.Cast<Capture>().Select((v, i) => new { value = v.Value, index = i }), a => a.index, b => b.index, (a, b) => new Fund { Code = a.Code, Name = a.Name, Type = b.value }).Where(w => (findstring is null || (w.Code.Contains(findstring) || w.Name.Contains(findstring) || w.Type.Contains(findstring))) && !w.Name.EndsWith("(后端)"));
return s;
}
获取更新基金明细数据
public static void GetFundDetail(Fund fundCode)
{
if (fundCode.Detail == null)
{
if (fundCode.Type == "货币型" || fundCode.Type == "QDII")
{
return;
}
string resultstr = Util.HttpCommon.HttpGet($"https://fund.eastmoney.com/pingzhongdata/{fundCode.Code}.js?v={DateTime.Now.ToString("yyyyMMddHHmmss")}");
if (resultstr.StartsWith("远程服务器返回错误"))
{
return;
}
Regex regex = new Regex("var Data_netWorthTrend = (?<Data>\\[(\\{\"x\"\\:(?<Date>[0-9]{13})\\,\"y\"\\:(?<Value>.*?)\\,.*?\\}[\\,]{0,1})+\\])");
Match match = regex.Match(resultstr);
fundCode.StartRecordTimeSpan = match.Groups["Date"].Captures.Cast<Capture>().First().Value;
fundCode.LastRecordTimeSpan = match.Groups["Date"].Captures.Cast<Capture>().Last().Value;
//fundCode.Detail =
fundCode.Detail = match.Groups["Date"].Captures.Cast<Capture>().Select((v, i) => new { index = i, value = Convert.ToSingle(v.Value) / 100000 }).Join(match.Groups["Value"].Captures.Cast<Capture>().Select((v, i) => new { index = i, value = Convert.ToDouble(v.Value) }), a => a.index, b => b.index, (a, b) => new Point { X = a.value, Y = b.value });
}
//DataService.UpdateFundDetail(fundCode);
}
也就是只要想从网上新的一种数据,就需要重新创建一个方法
但是访问网络数据具有共同的逻辑
即
0.判断输入的合法性,调用网络接口的前提条件
1.根据输入的url发送一个HTTP Get请求,接收string结果
2.处理返回异常
3.通过正则解析结果,
4.将结果拼接成List列表返回
通过泛型可以只声明一个方法,向调用方暴露必要的输入信息,以及需要的返回类型数据
泛型实现
/// <summary>
/// 可数据服务的,使用泛型获取数据
/// </summary>
public static class DataServiceable
{
public static IEnumerable<T> GetData<T>(this DataService S, string url, string regexString, Func<T, string, string, bool> CallBackSetting) where T : new()
{
Regex regex = new Regex("\\(\\?\\<(?<Key>.*?)\\>");
MatchCollection matchs = regex.Matches(regexString);
List<string> keys = matchs.Cast<Match>().Select(s => s.Groups["Key"].Value).ToList();
regex = new Regex(regexString);
string resultstr = Util.HttpCommon.HttpGet(url);
if (resultstr.StartsWith("远程服务器返回错误"))
{
return null;
}
Match match = regex.Match(resultstr);
List<T> a = new List<T>();
T t;
foreach (string item in keys)
{
for (int i = 0; i < match.Groups[item].Captures.Count; i++)
{
if (a.Count > i)
{
t = a[i];
CallBackSetting(t, item, match.Groups[item].Captures[i].Value);
}
else
{
t = new T();
if (CallBackSetting(t, item, match.Groups[item].Captures[i].Value))
{
a.Add(t);
}
}
}
}
return a;
}
}
定义了一个扩展方法,向DataService的实例添加一个GetData方法,其中GetData<T>让用户确定T是什么类型,系统返回List<T>
public static IEnumerable<T> GetData<T>(this DataService S, string url,
string regexString,
Func<T, string, string, bool> CallBackSetting) where T : new()
{...}
系统向调用方暴露需要输入的web地址,符合格式的正则字符串,以及如何将正则字符串中的标签和输出表字段关联起来的赋值逻辑,最后一个where T : new()?表示泛型必须要有一个无参的构造函数,这时系统就可以在内部直接创建一个泛型T的实例
t = new T();
调用委托调用方实现的方法
CallBackSetting(t, item, match.Groups[item].Captures[i].Value)
让调用方对泛型T数实例赋值
最终将赋值好的t添加到泛型List<T>的实例
List<T> a = new List<T>();
T t;
。。。
t = new T();
if (CallBackSetting(t, item, match.Groups[item].Captures[i].Value))
{
a.Add(t);
}
。。。
return a;
调用泛型方法
DataService dataService = new DataService();
List<Fund> funds =dataService.GetData<Fund>(
@"http://fund.eastmoney.com/js/fundcode_search.js?v=" + DateTime.Now.ToString("yyyyMMddHHmmss"), "var r = (?<Data>\\[(\\[\"(?<Code>[0-9]{6})\"\\,\".*?\"\\,\"(?<Name>.*?)\"\\,\"(?<Type>.*?)\".*?\\][\\,]{0,1})+\\])",
(a, capture, u) => {
switch (capture)
{
case "Code":
a.Code = u;
break;
case "Type":
a.Type = u;
break;
case "Name":
a.Name = u;
break;
default:
return false;
}
return true;
}).Where(w => (findstring is null || (w.Code.Contains(findstring) || w.Name.Contains(findstring) || w.Type.Contains(findstring))) && !w.Name.EndsWith("(后端)"));
直接返回一个LIst<Fund>数据集
如果要以相同的方式获取其他的网络数据
List<Point> points = dataService.GetData<Point>(
$"https://fund.eastmoney.com/pingzhongdata/{fundCode.Code}.js?v={DateTime.Now.ToString("yyyyMMddHHmmss")}", "var Data_netWorthTrend = (?<Data>\\[(\\{\"x\"\\:(?<Date>[0-9]{13})\\,\"y\"\\:(?<Value>.*?)\\,.*?\\}[\\,]{0,1})+\\])",
(a, capture, u) =>
{
switch (capture)
{
case "Date":
a.X = Convert.ToSingle(u) / 100000;
break;
case "Value":
a.Y = Convert.ToDouble(u);
break;
default:
return false;
}
return true;
});
委托方法通过Lameda表达式实现,系统将正则匹配的结果和正则标签作为传入参数,让调用方如何确定将该标签的值传给实例元素的哪个字端,调用方返回一个bool类型数据,告诉系统是否有赋值操作,系统根据这个返回值确定是否需要将让调用方更新后的实例添加到List列表中。
系统直接返回一个点集List<Point>
调用方完全不需要关心系统是如何将通过地址去访问网络数据,如何处理异常,如何处理正则匹配,如何构造List和添加元素,只需要调用方将需要的信息声明出来,系统就知道如何处理。
|