利用反射快速给Model实体赋值 使用 Task 简化异步编程 Guid ToString 格式知多少?(GUID 格式) Parallel Programming-实现并行操作的流水线(生产者、消费者) c# 无损高质量压缩图片代码 8种主要排序算法的C#实现 (一) 8种主要排序算法的C#实现 (二)
试想这样一个业务需求:有一张合同表,由于合同涉及内容比较多所以此表比较庞大,大概有120多个字段。现在合同每一次变更时都需要对合同原始信息进行归档一次,版本号依次递增。那么我们就要新建一张合同历史表,字段跟原合同表一模一样,此外多了一个 合同版本号 字段。在归档时如何把原始合同信息插入到合同历史表呢?
很容易就能想到的一种解决方法:
insert into 合同历史表(字段1,字段2,字段3…………字段120,版本号) select 字段1,字段2,字段3…………字段120,0 as 版本号 from 合同表 where 合同ID=‘xxxxxxx’
这样当然是能实现我们的功能的,但是看到了吗?由于表的字段太多,导致SQL看起来很不优雅,而且字段之间的对应很容易出问题。联想起之前看过的 利用反射给model赋值的例子想出来下面的一个解决方法:
现在假设两个实体class1、class2,class2只比class1多一个字段newid,其它字段一模一样。简单定义如下:
class Class1
{
private string id;
public string Id
{
get { return id; }
set { id = value; }
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
private int num;
public int Num
{
get { return num; }
set { num = value; }
}
}
class Class2
{
private string newid;
public string Newid
{
get { return newid; }
set { newid = value; }
}
private string id;
public string Id
{
get { return id; }
set { id = value; }
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
private int num;
public int Num
{
get { return num; }
set { num = value; }
}
}
下面我们给class1赋值,然后通过反射获取class2的属性,循环把class1对应的值赋给class2,遇到class2多出的字段我们手功处理后跳过。简单代码如下:
Class1 c1 = new Class1();
c1.Id = "001";
c1.Name = "ben.jiang";
c1.Num = 712104195;
c1.Age = 24;
Class2 c2 = new Class2();
Type t2 = typeof(Class2);
PropertyInfo[] propertys2 = t2.GetProperties();
Type t1 = typeof(Class1);
PropertyInfo[] propertys1 = t1.GetProperties();
foreach (PropertyInfo pi in propertys2)
{
string name = pi.Name;
if (name == "Newid")
{
c2.Newid = "newid";
continue;
}
object value=t1.GetProperty(name).GetValue(c1, null);
t2.GetProperty(name).SetValue(c2,value ,null);
}
这样代码看起来稍微优雅了一些,而且针对不同的字段我们处理起来也方便。
.Net 传统异步编程概述
.NET Framework 提供以下两种执行 I/O 绑定和计算绑定异步操作的标准模式:
- 异步编程模型 (APM),在该模型中异步操作由一对 Begin/End 方法(如 FileStream.BeginRead 和 Stream.EndRead)表示。
- 基于事件的异步模式 (EAP),在该模式中异步操作由名为“操作名称Async”和“操作名称Completed”的方法/事件对(例如 WebClient.DownloadStringAsync 和 WebClient.DownloadStringCompleted)表示。 (EAP 是在 .NET Framework 2.0 版中引入的)。
Task 的优点以及功能
通过使用 Task 对象,可以简化代码并利用以下有用的功能:
- 在任务启动后,可以随时以任务延续的形式注册回调。
- 通过使用 ContinueWhenAll 和 ContinueWhenAny 方法或者 WaitAll 方法或 WaitAny 方法,协调多个为了响应 Begin_ 方法而执行的操作。
- 在同一 Task 对象中封装异步 I/O 绑定和计算绑定操作。
- 监视 Task 对象的状态。
- 使用 TaskCompletionSource 将操作的状态封送到 Task 对象。
使用 Task 封装常见的异步编程模式
1、 使用 Task 对象封装 APM 异步模式, 这种异步模式是 .Net 标准的异步模式之一, 也是 .Net 最古老的异步模式, 自 .Net 1.0 起就开始出现了,通常由一对 Begin/End 方法同时出现, 以 WebRequest 的 BeginGetResponse 与 EndGetResponse 方法为例:
var request = WebRequest.CreateHttp(UrlToTest); request.Method = "GET"; var requestTask = Task.Factory.FromAsync<WebResponse>( request.BeginGetResponse, request.EndGetResponse, null ); requestTask.Wait(); var response = requestTask.Result;
2、使用 Task 对象封装 EPM 异步模式, 这种模式从 .Net 2.0 开始出现, 同时在 Silverlight 中大量出现, 这种异步模式以 “操作名称Async” 函数和 “操作名称Completed” 事件成对出现为特征, 以 WebClient 的 DownloadStringAsync 方法与 DownLoadStringCompleted 事件为例:
var source = new TaskCompletionSource<string>();
var webClient = new WebClient();
webClient.DownloadStringCompleted += (sender, args) => {
if (args.Cancelled) {
source.SetCanceled();
return;
}
if (args.Error != null) {
source.SetException(args.Error);
return;
}
source.SetResult(args.Result);
};
webClient.DownloadStringAsync(new Uri(UrlToTest, UriKind.Absolute), null);
source.Task.Wait();
var result = source.Task.Result;
3、 使用 Task 对象封装其它非标准异步模式, 这种模式大量出现在第三方类库中, 通常通过一个 Action 参数进行回调, 以下面的方法为例:
void AddAsync(int a, int b, Action<int> callback)
封装方法与封装 EPM 异步模式类似:
var source = new TaskCompletionSource<int>(); Action<int> callback = i => source.SetResult(i); AddAsync(1, 2, callback); source.Task.Wait(); var result = source.Task.Result;
通过上面的例子可以看出, 用 Task 对象对异步操作进行封装之后, 异步操作简化了很多, 只要调用 Task 的 Wait 方法, 可以直接获取异步操作的结果, 而不用转到回调函数中进行处理, 接下来看一个比较实际的例子。
缓冲查询示例
以 Esri 提供的缓冲查询为例, 用户现在地图上选择一个合适的点, 按照一定半径查询查询缓冲区, 再查询这个缓冲区内相关的建筑物信息, 这个例子中, 我们需要与服务端进行两次交互:
- 根据用户选择的点查询出缓冲区;
- 查询缓冲区内的建筑物信息;
这个例子在 GIS 查询中可以说是非常简单的, 也是很典型的, ESRI 的例子中也给出了完整的源代码, 这个例子的核心逻辑代码是:
_geometryService = new GeometryService(GeoServerUrl);
_geometryService.BufferCompleted += GeometryService_BufferCompleted;
_queryTask = new QueryTask(QueryTaskUrl);
_queryTask.ExecuteCompleted += QueryTask_ExecuteCompleted;
void MyMap_MouseClick(object sender, Map.MouseEventArgs e) {
// 部分代码省略, 开始缓冲查询
_geometryService.BufferAsync(bufferParams);
}
void GeometryService_BufferCompleted(object sender, GraphicsEventArgs args) {
// 部分代码省略, 获取缓冲查询结果, 开始查询缓冲区内的建筑物信息
_queryTask.ExecuteAsync(query);
}
void QueryTask_ExecuteCompleted(object sender, QueryEventArgs args) {
// 将查询结果更新到界面上
}
这只是一个 GIS 开发中很简单的一个查询, 上面的代码却将逻辑分散在三个函数中, 在实际应用中, 与服务端的交互次数会更多, 代码的逻辑会分散在更多的函数中, 导致代码的可读性以及可维护性降低。 如果使用 Task 对象对这些任务进行封装, 那么整个逻辑将会简洁很多, GeometryService 和 QueryTask 提供的是 EPM 异步模式, 相应的封装方法如上所示, 最后, 用 Task 封装异步操作之后的代码如下:
void MyMap_MouseClick(object sender, Map.MouseEventArgs e) {
Task.Factory.StartNew(() => {
// 省略部分 UI 代码, 开始缓冲查询
var bufferParams = new BufferParameters() { /* 初始化缓冲查询参数 */};
var bufferTask = _geometryService.CreateBufferTask()
// 等待缓冲查询结果
bufferTask.Wait();
// 省略更新 UI 的代码, 开始查询缓冲区内的建筑物信息
var query = new Query() { /* 初始化查询参数 */ };
var queryExecTask = _queryTask.CreateExecTask(query);
queryExecTask.Wait();
// 将查询结果显示在界面上, 代码省略
});
}
从上面的代码可以看出, 使用 Task 对象可以把原本分散在三个函数中的逻辑集中在一个函数中即可完成, 代码的可读性、可维护性比原来增加了很多。
Task 能完成的任务远不止这些,比如并行计算、 协调多个并发任务等, 有兴趣的可以进一步阅读相关的 MSDN 资料。
在日常编程中,Guid是比较常用的,最常见的使用就是如下所示:
string id = Guid.NewGuid().ToString();
这条语句会生成一个新的Guid并转成字符串,如下:
// 10244798-9a34-4245-b1ef-9143f9b1e68a
但是还有一些情况下,我们会有一些细节上的差异,如:
- 前后有大括号{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
- 中间没有连字符 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- 前后是圆括号(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
遇到这种情况就比较麻烦了,最常见的一种就是自己把guid生成的字符串解析处理,比如加括号,或者把连字符用空字符替换掉等:
var str = guid.ToString();
var id = "{" + str + "}";
var id2 = str.Replace("-", "");
var id3 = "(" + str + ")";
其实不用这么麻烦的,在ToString的时候,有一个重载的函数:
ToString(String)
通过传入格式化字符串,就可以输出这种类型的guid 字符串了。
示例如下:
var guid = Guid.NewGuid();
// 10244798-9a34-4245-b1ef-9143f9b1e68a
Console.WriteLine(guid.ToString("D"));
// 102447989a344245b1ef9143f9b1e68a
Console.WriteLine(guid.ToString("N"));
// {10244798-9a34-4245-b1ef-9143f9b1e68a}
Console.WriteLine(guid.ToString("B"));
// (10244798-9a34-4245-b1ef-9143f9b1e68a)
Console.WriteLine(guid.ToString("P"));
注意:这里的D,N,B,P是不区分大小写的,如果传入空字符串,则使用的默认的D类型,其它情况都会报异常。
在MSDN中查询到还有一种“X”类型,但是我在.NetFx 3.5下,使用时会弹出异常:
未处理的异常: System.FormatException: 格式字符串只能是“D”、“d”、“N”、“n ”、“P”、“p”、“B”或“b”。
在 System.Guid.ToString(String format, IFormatProvider provider)
本文介绍如何使用C#实现并行执行的流水线(生产者消费者):
1.流水线示意图
2.实现并行流水线
一、流水线示意图
上图演示了流水线,action1接收input,然后产生结果保存在buffer1中,action2读取buffer1中由action1产生的数据,以此类推指导action4完成产生Output。
以上也是典型的生产者消费者模式。
上面的模式如果使用普通常规的串行执行是很简单的,按部就班按照流程图一步一步执行即可。如果为了提高效率,想使用并行执行,也就是说生产者和消费者同时并行执行,该怎么办么?
二、实现并行流水线
2.1 代码
class PiplelineDemo { private int seed; public PiplelineDemo() { seed = 10; } public void Action1(BlockingCollection<string> output) { try { for (var i = 0; i < seed; i++) { output.Add(i.ToString());//initialize data to buffer1 } } finally { output.CompleteAdding(); } } public void Action2(BlockingCollection<string> input, BlockingCollection<string> output) { try { foreach (var item in input.GetConsumingEnumerable()) { var itemToInt = int.Parse(item); output.Add((itemToInt * itemToInt).ToString());// add new data to buffer2 } } finally { output.CompleteAdding(); } } public void Action3(BlockingCollection<string> input, BlockingCollection<string> output) { try { foreach (var item in input.GetConsumingEnumerable()) { output.Add(item);//set data into buffer3 } } finally { output.CompleteAdding(); } } public void Pipeline() { var buffer1 = new BlockingCollection<string>(seed); var buffer2 = new BlockingCollection<string>(seed); var buffer3 = new BlockingCollection<string>(seed); var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None); var stage1 = taskFactory.StartNew(() => Action1(buffer1)); var stage2 = taskFactory.StartNew(() => Action2(buffer1, buffer2)); var stage3 = taskFactory.StartNew(() => Action3(buffer2, buffer3)); Task.WaitAll(stage1, stage2, stage3); foreach(var item in buffer3.GetConsumingEnumerable())//print data in buffer3 { Console.WriteLine(item); } } } class Program { static void Main(string[] args) { new PiplelineDemo().Pipeline(); Console.Read(); } }2.2 运行结果
预期打印出了0-9自我相乘的结果。
2.3 代码解释
代码本身的逻辑和本文开始的流程图是一一对应的。
BlockingCollection<T>是.Net里面的一个线程安全集合。实现了IProducerConsumerCollection<T>.
- Add方法:将元素加入集合
- CompleteAdding方法:告诉消费者,在当调用该方法之前的元素处理完之后就不要再等待处理了,可以结束处理了。这个非常重要,一定要执行,所以放在finally中(就算exception也要执行)
- GetConsumingEnumberable,给消费者返回一个可以便利的集合
在CSDN上看到了一个压缩算法:http://blog.csdn.net/qq_16542775/article/details/51792149
进过测试这个算法,发现,将原始图像的大小进行对半处理,然后迭代跳转压缩质量参数,可以得到不错的效果。
/// <summary> /// 无损压缩图片 /// </summary> /// <param name="sFile">原图片地址</param> /// <param name="dFile">压缩后保存图片地址</param> /// <param name="flag">压缩质量(数字越小压缩率越高)1-100</param> /// <param name="size">压缩后图片的最大大小</param> /// <param name="sfsc">是否是第一次调用</param> /// <returns></returns> public static bool CompressImage(string sFile, string dFile, int flag = 90, int size = 300, bool sfsc = true) { //如果是第一次调用,原始图像的大小小于要压缩的大小,则直接复制文件,并且返回true FileInfo firstFileInfo = new FileInfo(sFile); if (sfsc == true && firstFileInfo.Length < size * 1024) { firstFileInfo.CopyTo(dFile); return true; } Image iSource = Image.FromFile(sFile); ImageFormat tFormat = iSource.RawFormat; int dHeight = iSource.Height / 2; int dWidth = iSource.Width / 2; int sW = 0, sH = 0; //按比例缩放 Size tem_size = new Size(iSource.Width, iSource.Height); if (tem_size.Width > dHeight || tem_size.Width > dWidth) { if ((tem_size.Width * dHeight) > (tem_size.Width * dWidth)) { sW = dWidth; sH = (dWidth * tem_size.Height) / tem_size.Width; } else { sH = dHeight; sW = (tem_size.Width * dHeight) / tem_size.Height; } } else { sW = tem_size.Width; sH = tem_size.Height; } Bitmap ob = new Bitmap(dWidth, dHeight); Graphics g = Graphics.FromImage(ob); g.Clear(Color.WhiteSmoke); g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.DrawImage(iSource, new Rectangle((dWidth - sW) / 2, (dHeight - sH) / 2, sW, sH), 0, 0, iSource.Width, iSource.Height, GraphicsUnit.Pixel); g.Dispose(); //以下代码为保存图片时,设置压缩质量 EncoderParameters ep = new EncoderParameters(); long[] qy = new long[1]; qy[0] = flag;//设置压缩的比例1-100 EncoderParameter eParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, qy); ep.Param[0] = eParam; try { ImageCodecInfo[] arrayICI = ImageCodecInfo.GetImageEncoders(); ImageCodecInfo jpegICIinfo = null; for (int x = 0; x < arrayICI.Length; x++) { if (arrayICI[x].FormatDescription.Equals("JPEG")) { jpegICIinfo = arrayICI[x]; break; } } if (jpegICIinfo != null) { ob.Save(dFile, jpegICIinfo, ep);//dFile是压缩后的新路径 FileInfo fi = new FileInfo(dFile); if (fi.Length > 1024 * size) { flag = flag - 10; CompressImage(sFile, dFile, flag, size, false); } } else { ob.Save(dFile, tFormat); } return true; } catch { return false; } finally { iSource.Dispose(); ob.Dispose(); } }





浙公网安备 33010602011771号