在C#语言中,回调是通过委托来实现的。委托为我们提供了安全的回调定义,大多数委托都和事件相关,但这不是委托的全部应用场景,当类之间需要通信,并且我们希望一种比接口更加松耦合的机制时,委托便是最佳选择。委托允许我们在运行时配置目标并且通知多个客户对象,委托对象中包含一个引用,该方法可以使静态方法也可以是实例方法。使用委托,我们可以和一个或者多个在运行时联系起来的客户对象进行通信。
Lambda表达式
回调和委托在C#中非常常用,C#为委托提供了精简的语法——Lambda表达式。同时.NET还内建了很多常用的委托形式,它们包含在这三个泛型委托中:
- Predicate<T>:表示一个提供bool返回值的函数,用来测试某个条件。
- Action<T>:接收一系列参数,返回单一结果。
- Func<T>:接收任意参数数目,但不返回任何值。
Linq中大量使用了这些概念,List<T>中也包含了很多的回调方法,如下:
2 {
3 List<int> numbers = Enumerable.Range(1, 200).ToList();
4 var oddNumbers = numbers.Find(n => n % 2 == 1);//返回集合中匹配的第一个元素
5 var test = numbers.TrueForAll(n => n < 50);//是否集合中每个元素符合定义的条件,返回false
6 numbers.RemoveAll(n => n % 2 == 0);//删除所有偶数
7 numbers.ForEach(item => Console.WriteLine(item));//打印集合中所有元素
8
9 Console.Read();
10 }
LINQ所有的功能都依赖于委托。回调也是用在WPF和Windows Forms中跨线程调用的封装(marshalling)上。在.NET Framework中需要传入方法的地方,框架都会使用委托,并允许调用者使用Lambda表达式提供。
多播委托
C#中的委托都是多播委托(multicast delegate)。多播委托将会把所有添加到委托中的目标函数组合成一个单一的调用。在这里我们需要注意两点:
1.如果有委托调用出现异常,那么委托链会被中断,这种方式不能保证安全。
2.多播委托返回的将是委托链上最后一个函数调用的返回值。
为了解决上的问题我们可以手动遍历委托链中的每个委托目标(可以参考:使用委托和事件实现观察者模式(Observer Pattern)),通过调用委托实例的GetInvocationList(),遍历委托链中的每个目标。看下面的示例:只有每一个委托调用都返回true时,遍历才能继续。或者我们可以再遍历委托链时添加try/catch语句块,处理异常。
2 {
3 bool bContinue = true;
4 foreach (ComplicatedClass cl in container)
5 {
6 c1.DoLengthyOperation();
7 foreach (Func<bool> pr in Pred.GetInvocationList())
8 {
9 bContinue &= pr();//等效于 bContinue = bContinue & pr();
10
11 if (!bContinue)
12 return;
13 }
14 }
15 }
小节
委托是在运行时进行回调的最好方式,这种方式对客户类的要求更加简单,你可以在运行时配置委托目标。另外,委托也支持多个客户目标。在.NET中,客户回调应该使用委托来实现。
本文链接
调用过WCF服务的同学可能都会遇到这样的问题,同一个实体类型,不同的服务Visual Studio生成了不同的版本,例如Service1.User和Service2.User,对于C#来说,这是两个不同的类型,Service1获得的User是放不到Service2服务里去的。手动的属性赋值来转换显然是不可取的,所以就共享类型了。
方法1,服务端和客户端共享数据契约程序集。
这个方法最常用,也是大家最熟悉的方法,把WCF的数据契约放在一个独立类库里,服务端,客户端都引用这个程序集,然后在生成WCF时,选择重新使用引用程序集中的类型即可。
这个方法缺点很明显,它只有在客户端和服务端在同一个Visual Studio解决方案内才方便,否则要不断手动更新数据契约程序集。更不用说是第三方的服务。
方法二,暴力转换类型
这个其实不是类型共享,不过也是解决这个问题的一种手段。就是借助AutoMapper,EmitMapper这样的类库帮助快速转换类型。下面是一个例子。
Money类型包含User实体和Currency枚举和一个数字的Amount,Money的定义
[DataContract(Namespace = Consts.Namespace)]public class Money
{
[DataMember]
public decimal Amount { get; set; }
[DataMember]
public Currency Currency { get; set; }
[DataMember]
public UserInfo User { get; set; }
}
Currency:
[DataContract(Namespace = Consts.Namespace)]public enum Currency
{
[EnumMember]
Euro,
[EnumMember]
Usd,
[EnumMember]
PoundSterling
}
UserInfo:
[DataContract(Namespace = Consts.Namespace)]public class UserInfo
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
[DataMember]
public string Email { get; set; }
[DataMember]
public string Phone { get; set; }
[DataMember]
public string Id { get; set; }
}
对于DepositServiceNoSharp和WithdrawalServiceNoSharp这两个WCF服务版本的Money和User,可以这样添加一些扩展方法
using AutoMapper;using DepositService = Client.DepositServiceNoSharp;
using Client.WithdrawalServiceNoSharp;
namespace Client
{
public static class Extensions
{
static Extensions()
{
Mapper.CreateMap<DepositService.Money, Money>();
Mapper.CreateMap<Money, DepositService.Money>();
Mapper.CreateMap<DepositService.UserInfo, UserInfo>();
Mapper.CreateMap<UserInfo, DepositService.UserInfo>();
}
public static Money ToWithdrawal(this DepositService.Money money)
{
return Mapper.Map<DepositService.Money, Money>(money);
}
public static DepositService.Money ToDeposit(this Money money)
{
return Mapper.Map<Money, DepositService.Money>(money);
}
}
}
然后就可以轻松转换
var money = new Money{
Amount = 1,
Currency = Currency.Usd,
User = new UserInfo
{
Email = "zhww@outlook.com",
FirstName = "zhang",
Id = "123",
LastName = "weiwen",
Phone = "110"
}
};
var depositMoney = money.ToDeposit();
方法三,使用SvcMap实现类型共享
其实这个才是文章的重点,前面可以忽略。
生成第一个WCF服务后,点击”显示所有文件“去编辑SvcMap文件:
找到MetadataSources节点,原来只有一个,现在把其他要引用的服务添加到这里,例如:
<MetadataSources><MetadataSource Address="http://localhost:34875/DepositService.svc" Protocol="http" SourceId="1" />
<MetadataSource Address="http://localhost:34875/WithdrawalService.svc" Protocol="http" SourceId="2" />
</MetadataSources>
再右击服务,”更新服务引用“,所有服务都会生成到同一个命名空间里,实现类型共享。
最后感谢原作者,原文链接。
去年早些时候的在博客中分享过一篇文章 通过Socket进行HttP/HTTPS网页操作 ,之后在另外一文章 Socket模拟HTTP协议之火车票购票软件 中用到了该帮助类.
时隔一年之后,在偶尔的使用过程中我将该类重新修复修改了一下:
1、增加了对chunked协议的解析
2、修改了一些API接口
3、修复了一些BUG
就在最近也还有同学找到我请教该类的一些用法,所以再次整理了一下发布出来,希望对需要的朋友有所帮助。
不过,目前该类的一些封装和API接口还不是特别友好,有需要的朋友可自行理解后修改,我也很善于帮不懂的朋友解答一些问题。
如若以后再有新的修改我也会同样共享出来。
需要Socket方式提交HTTP的同学请无视HttpWebRequest方式的部分;
需要返回编码的同学我想说的是,许多在做采集的同学都希望能智能的识别返回内容的编码,我个人觉着没多大必要,
因为大多时候大家要采集的目标是确定的,那其内容编码也是确定的,通过一些硬编码或者配置文件可解决的问题没必要陷入一个误区,一定做智能编码识别反倒吃力不讨好。
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
/************************************************************************/
/* Author:huliang
* Email:huliang@yahoo.cn
* 注意:转载请注明出处
/************************************************************************/
namespace LiangHu
{
/// <summary>
/// HTTP协议头包装
/// </summary>
public class HttpHeader
{
public HttpHeader()
: this("")
{
}
public HttpHeader(string url)
{
this.Url = url;
}
public string Url
{
get;
set;
}
public string Host
{
get;
set;
}
public string Accept
{
get;
set;
}
public string Referer
{
get;
set;
}
public string Cookies
{
get;
set;
}
public string Body
{
get;
set;
}
Dictionary<string, string> m_Others = new Dictionary<string, string>();
public string this[string key]
{
get
{
return m_Others.ContainsKey(key) ? m_Others.ContainsKey(key) : null;
}
set
{
Add(key, value);
}
}
public void Add(string key, string value)
{
switch (key.ToUpper())
{
case "URL":
this.Url = value;
break;
case "HOST":
this.Host = value;
break;
case "ACCEPT":
this.Accept = value;
break;
case "REFERER":
this.Referer = value;