1. IsAssignableFrom实例方法 判断一个类或者接口是否继承自另一个指定的类或者接口。
public interface IAnimal { } public interface IDog : IAnimal { } public class Dog : IDog { } public class Cate : IAnimal { } public class Parrot { }
var iAnimalType = typeof(IAnimal); var iDogType = typeof(IDog); var dogType = typeof(Dog); var cateType = typeof(Cate); var parrotType = typeof(Parrot); Console.WriteLine(iAnimalType.IsAssignableFrom(iDogType) ? $"{iDogType.Name} was inherited from {iAnimalType.Name}" : $"{iDogType.Name} was not inherited from {iAnimalType.Name}"); Console.WriteLine(iAnimalType.IsAssignableFrom(dogType) ? $"{dogType.Name} was inherited from {iAnimalType.Name}" : $"{dogType.Name} was not inherited from {iAnimalType.Name}"); Console.WriteLine(iDogType.IsAssignableFrom(dogType) ? $"{dogType.Name} was inherited from {iDogType.Name}" : $"{dogType.Name} was not inherited from {iDogType.Name}"); Console.WriteLine(iAnimalType.IsAssignableFrom(cateType) ? $"{cateType.Name} was inherited from {iAnimalType.Name}" : $"{cateType.Name} was not inherited from {iAnimalType.Name}"); Console.WriteLine(iAnimalType.IsAssignableFrom(parrotType) ? $"{parrotType.Name} inherited from {iAnimalType.Name}" : $"{parrotType.Name} not inherited from {iAnimalType.Name}"); Console.ReadKey();
输出结果:
IDog was inherited from IAnimal
Dog was inherited from IAnimal
Dog was inherited from IDog
Cate was inherited from IAnimal
Parrot not inherited from IAnimal
2.IsInstanceOfType 判断某个对象是否继承自指定的类或者接口
Dog d=new Dog(); var result=typeof(IDog).IsInstanceOfType(d); Console.WriteLine(result? $"Dog was inherited from IDog": $"Dog was not inherited from IDog"); Console.ReadKey();
输出结果:
Dog was inherited from IDog
3.IsSubclassOf 判断一个对象的类型是否继承自指定的类,不能用于接口的判断,这能用于判定类的关系
public interface IAnimal { } public interface IDog : IAnimal { } public class Dog : IDog { } public class Husky : Dog { } public class Cate : IAnimal { } public class Parrot { }
Husky husky = new Husky(); var result = husky.GetType().IsSubclassOf(typeof(Dog)); Console.WriteLine(result ? $"Husky was inherited from Dog" : $"Husky was not inherited from Dog");
输出结果:
Husky was inherited from Dog
这个方法不能用于接口,如果穿接口进去永远返回的都是false
Dog dog = new Dog(); var dogResult = dog.GetType().IsSubclassOf(typeof(IDog)); Console.WriteLine(dogResult);
结果:
false
C#中关于增强类功能的几种方式
C#中关于增强类功能的几种方式
本文主要讲解如何利用C#语言自身的特性来对一个类的功能进行丰富与增强,便于拓展现有项目的一些功能。
拓展方法
扩展方法被定义为静态方法,通过实例方法语法进行调用。方法的第一个参数指定该方法作用于哪个类型,并且该参数以 this 修饰符为前缀。仅当使用 using 指令将命名空间显式导入到源代码中之后,扩展方法才可使用。
namespace Extensions
{
public static class StringExtension
{
public static DateTime ToDateTime(this string source)
{
DateTime.TryParse(source, out DateTime result);
return result;
}
}
}
注意:
- 如果扩展方法与该类型中定义的方法具有相同的签名,则扩展方法永远不会被调用。
- 在命名空间级别将扩展方法置于相应的作用范围内。例如,在一个名为 Extensions 的命名空间中具有多个包含扩展方法的静态类,则在使用这些拓展方法时,必须引用其命名空间 using Extensions
继承
继承 面向对象的一个特性,属于Is a 关系,比如说Student继承Person,则说明Student is a Person。子类可以通过重写父类的方法或添加新的方法来实现对父类的拓展。
namespace Inherit
{
public class Persion
{
public string Name { get; set; }
public int Age { get; set; }
public void Eat()
{
Console.WriteLine("吃饭");
}
public void Sleep()
{
Console.WriteLine("睡觉");
}
}
public class Student : Persion
{
public void Study()
{
Console.WriteLine("学习");
}
public new void Sleep()
{
Console.WriteLine("做作业,复习功课");
base.Sleep();
}
}
}
继承的缺点:
- 父类的内部细节对子类是可见的
- 子类与父类的继承关系在编译阶段就确定下来了,无法在运行时动态改变从父类继承方法的行为
- 如果父类方法做了修改,所有的子类都必须做出相应的调整,子类与父类是一种高度耦合,违反了面向对象的思想。
组合
组合就是在设计类的时候把需要用到的类作为成员变量加入到当前类中。
组合的优缺点:
- 优点:
- 隐藏了被引用对象的内部细节
- 降低了两个对象之间的耦合
- 可以在运行时动态修改被引用对象的实例
- 缺点:
- 系统变更可能需要不停的定义新的类
- 系统结构变复杂,不再局限于单个类
建议多使用组合,少用继承
装饰者模式
装饰者模式指在不改变原类定义及继承关系的情况跟下,动态的拓展一个类的功能,就是利用创建一个包装类(wrapper)来装饰(decorator)一个已有的类。
包含角色:
- 被装饰者:
- Component 抽象被装饰者,
- ConcreteComponent 具体被装饰者,Component的实现,在装饰者模式中装饰的就是这货。
- 装饰者:
- Decorator 装饰者 一般是一个抽象类并且作为Component的子类,Decorator必然会有一个成员变量用来存储Component的实例
- ConcreateDecorator 具体装饰者 Decorator的实现
在装饰者模式中必然会有一个最基本,最核心,最原始的接口或抽象类充当component和decorator的抽象组件
实现要点:
- 定义一个类或接口,并且让装饰者及被装饰者的都继承或实现这个类或接口
- 装饰者中必须持有被装饰者的引用
- 装饰者中对需要增强的方法进行增强,不需要增强的方法调用原来的业务逻辑
namespace Decorator
{
/// <summary>
/// Component 抽象者装饰者
/// </summary>
public interface IStudent
{
void Learn();
}
/// <summary>
/// ConcreteComponent 具体被装饰者
/// </summary>
public class Student : IStudent
{
private string _name;
public Student(string name)
{
this._name = name;
}
public void Learn()
{
System.Console.WriteLine(this._name + "学习了以上内容");
}
}
/// <summary>
/// Decorator 装饰者
/// </summary>
public abstract class Teacher : IStudent
{
private IStudent _student;
public Teacher(IStudent student)
{
this._student = student;
}
public virtual void Learn()
{
this.Rest();
this._student.Learn();
}
public virtual void Rest()
{
Console.WriteLine("课间休息");
}
}
/// <summary>
/// ConcreteDecorator 具体装饰者
/// </summary>
public class MathTeacher : Teacher
{
private String _course;
public MathTeacher(IStudent student, string course) : base(student)
{
this._course = course;
}
public override void Learn()
{
System.Console.WriteLine("学习新内容:" + this._course);
base.Learn();
}
public override void Rest()
{
System.Console.WriteLine("课间不休息,开始考试");
}
}
/// <summary>
/// ConcreteDecorator 具体装饰者
/// </summary>
public class EnlishTeacher : Teacher
{
private String _course;
public EnlishTeacher(IStudent student, string course) : base(student)
{
this._course = course;
}
public override void Learn()
{
this.Review();
System.Console.WriteLine("学习新内容:" + this._course);
base.Learn();
}
public void Review()
{
System.Console.WriteLine("复习英文单词");
}
}
public class Program
{
static void Main(string[] args)
{
IStudent student = new Student("student");
student = new MathTeacher(student, "高数");
student = new EnlishTeacher(student, "英语");
student.Learn();
}
}
}
装饰者模式优缺点:
- 优点:
- 装饰者与被装饰者可以独立发展,不会互相耦合
- 可以作为继承关系的替代方案,在运行时动态拓展类的功能
- 通过使用不同的装饰者类或不同的装饰者排序,可以得到各种不同的结果
- 缺点:
- 产生很多装饰者类
- 多层装饰复杂
代理模式
代理模式就是给一个对象提供一个代理对象,并且由代理控制原对象的引用。
包含角色:
- 抽象角色:抽象角色是代理角色和被代理角色的所共同继承或实现的抽象类或接口
- 代理角色:代理角色是持有被代理角色引用的类,代理角色可以在执行被代理角色的操作时附加自己的操作
- 被代理角色:被代理角色是代理角色所代理的对象,是真实要操作的对象
静态代理
动态代理涉及到反射技术相对静态代理会复杂很多,掌握好动态代理对AOP技术有很大帮助
namespace Proxy
{
/// <summary>
/// 共同抽象角色
/// </summary>
public interface IBuyHouse
{
void Buy();
}
/// <summary>
/// 真实买房人,被代理角色
/// </summary>
public class Customer : IBuyHouse
{
public void Buy()
{
System.Console.WriteLine("买房子");
}
}
/// <summary>
/// 中介-代理角色
/// </summary>
public class CustomerProxy : IBuyHouse
{
private IBuyHouse target;
public CustomerProxy(IBuyHouse buyHouse)
{
this.target = buyHouse;
}
public void Buy()
{
System.Console.WriteLine("筛选符合条件的房源");
this.target.Buy();
}
}
public class Program
{
static void Main(string[] args)
{
IBuyHouse buyHouse = new CustomerProxy(new Customer());
buyHouse.Buy();
System.Console.ReadKey();
}
}
}
动态代理
namespace DynamicProxy
{
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
/// <summary>
/// 方法拦截器接口
/// </summary>
public interface IMethodInterceptor
{
/// <summary>
/// 调用拦截器
/// </summary>
/// <param name="targetMethod">拦截的目标方法</param>
/// <param name="args">拦截的目标方法参数列表</param>
/// <returns>拦截的目标方法返回值</returns>
object Interceptor(MethodInfo targetMethod, object[] args);
}
/// <summary>
/// 代理类生成器
/// </summary>
public class ProxyFactory : DispatchProxy
{
private IMethodInterceptor _interceptor;
/// <summary>
/// 创建代理类实例
/// </summary>
/// <param name="targetType">要代理的接口</param>
/// <param name="interceptor">拦截器</param>
/// <returns></returns>
public static object CreateInstance(Type targetType, IMethodInterceptor interceptor)
{
object proxy = GetProxy(targetType);
((ProxyFactory)proxy).GetInterceptor(interceptor);
return proxy;
}
/// <summary>
/// 创建代理类实例
/// </summary>
/// <param name="targetType">要代理的接口</param>
/// <param name="interceptorType">拦截器</param>
/// <param name="parameters">拦截器构造函数参数值</param>
/// <returns>代理实例</returns>
public static object CreateInstance(Type targetType, Type interceptorType, params object[] parameters)
{
object proxy = GetProxy(targetType);
((ProxyFactory)proxy).GetInterceptor(interceptorType, parameters);
return proxy;
}
/// <summary>
/// 创建代理类实例
/// </summary>
/// <typeparam name="TTarget">要代理的接口</typeparam>
/// <typeparam name="TInterceptor">拦截器</typeparam>
/// <param name="parameters">拦截器构造函数参数值</param>
/// <returns></returns>
public static TTarget CreateInstance<TTarget, TInterceptor>(params object[] parameters) where TInterceptor : IMethodInterceptor
{
object proxy = GetProxy(typeof(TTarget));
((ProxyFactory)proxy).GetInterceptor(typeof(TInterceptor), parameters);
return (TTarget)proxy;
}
/// <summary>
/// 获取代理类
/// </summary>
/// <param name="targetType"></param>
/// <returns></returns>
private static object GetProxy(Type targetType)
{
MethodCallExpression callexp = Expression.Call(typeof(DispatchProxy), nameof(DispatchProxy.Create), new[] { targetType, typeof(ProxyFactory) });
return Expression.Lambda<Func<object>>(callexp).Compile()();
}
/// <summary>
/// 获取拦截器
/// </summary>
/// <param name="interceptorType"></param>
/// <param name="parameters"></param>
private void GetInterceptor(Type interceptorType, object[] parameters)
{
Type[] ctorParams = parameters.Select(x => x.GetType()).ToArray();
IEnumerable<ConstantExpression> paramsExp = parameters.Select(x => Expression.Constant(x));
NewExpression newExp = Expression.New(interceptorType.GetConstructor(ctorParams), paramsExp);
this._interceptor = Expression.Lambda<Func<IMethodInterceptor>>(newExp).Compile()();
}
/// <summary>
/// 获取拦截器
/// </summary>
/// <param name="interceptor"></param>
private void GetInterceptor(IMethodInterceptor interceptor)
{
this._interceptor = interceptor;
}
/// <summary>
/// 执行代理方法
/// </summary>
/// <param name="targetMethod"></param>
/// <param name="args"></param>
/// <returns></returns>
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
return this._interceptor.Interceptor(targetMethod, args);
}
}
/// <summary>
/// 表演者
/// </summary>
public interface IPerform
{
/// <summary>
/// 唱歌
/// </summary>
void Sing();
/// <summary>
/// 跳舞
/// </summary>
void Dance();
}
/// <summary>
/// 具体的表演者——刘德华 Andy
/// </summary>
public class AndyPerformer : IPerform
{
public void Dance()
{
System.Console.WriteLine("给大家表演一个舞蹈");
}
public void Sing()
{
System.Console.WriteLine("给大家唱首歌");
}
}
/// <summary>
/// 经纪人——负责演员的所有活动
/// </summary>
public class PerformAgent : IMethodInterceptor
{
public IPerform _perform;
public PerformAgent(IPerform perform)
{
this._perform = perform;
}
public object Interceptor(MethodInfo targetMethod, object[] args)
{
System.Console.WriteLine("各位大佬,要我们家艺人演出清闲联系我");
object result = targetMethod.Invoke(this._perform, args);
System.Console.WriteLine("各位大佬,表演结束该付钱了");
return result;
}
}
public class Program
{
static void Main(string[] args)
{
IPerform perform;
//perform = ProxyFactory.CreateInstance<IPerform, PerformAgent>(new AndyPerformer());
//perform.Sing();
//perform.Dance();
ServiceCollection serviceDescriptors = new ServiceCollection();
serviceDescriptors.AddSingleton<IPerform>(ProxyFactory.CreateInstance<IPerform, PerformAgent>(new AndyPerformer()));
IServiceProvider serviceProvider = serviceDescriptors.BuildServiceProvider();
perform = serviceProvider.GetService<IPerform>();
perform.Sing();
perform.Dance();
System.Console.ReadKey();
}
}
}
总结
- 使用拓展方法只能拓展新增方法,不能增强已有的功能
- 使用继承类或接口,类只能单继承,并且在父类改变后,所有的子类都要跟着变动
- 使用代理模式与继承一样代理对象和真实对象之间的的关系在编译时就确定了
- 使用装饰者模式能够在运行时动态地增强类的功能
参考引用
利用.NET Core类库System.Reflection.DispatchProxy实现简易Aop
Asp.Net Core 轻松学-多线程之取消令牌
前言
取消令牌(CancellationToken) 是 .Net Core 中的一项重要功能,正确并合理的使用 CancellationToken 可以让业务达到简化代码、提升服务性能的效果;当在业务开发中,需要对一些特定的应用场景进行深度干预的时候,CancellationToken 将发挥非常重要的作用。
1. 多线程请求合并数据源
在一个很常见的业务场景中,比如当请求一个文章详细信息的时候,需要同时加载部分点赞用户和评论内容,这里一共有 3 个任务,如果按照常规的先请求文章信息,然后再执行请求点赞和评论,那么我们需要逐一的按顺序去数据库中执行 3 次查询;但是利用 CancellationToken ,我们可以对这 3 个请求同时执行,然后在所有数据源都请求完成的时候,将这些数据进行合并,然后输出到客户端
1.1 合并请求文章信息
public static void Test()
{
Random rand = new Random();
CancellationTokenSource cts = new CancellationTokenSource();
List<Task<Article>> tasks = new List<Task<Article>>();
TaskFactory factory = new TaskFactory(cts.Token);
foreach (var t in new string[] { "Article", "Post", "Love" })
{
Console.WriteLine("开始请求");
tasks.Add(factory.StartNew(() =>
{
var article = new Article { Type = t };
if (t == "Article")
{
article.Data.Add("文章已加载");
}
else
{
for (int i = 1; i < 5; i++)
{
Thread.Sleep(rand.Next(1000, 2000));
Console.WriteLine("load:{0}", t);
article.Data.Add($"{t}_{i}");
}
}
return article;
}, cts.Token));
}
Console.WriteLine("开始合并结果");
foreach (var task in tasks)
{
Console.WriteLine();
var result = task.Result;
foreach (var d in result.Data)
{
Console.WriteLine("{0}:{1}", result.Type, d);
}
task.Dispose();
}
cts.Cancel();
cts.Dispose();
Console.WriteLine("\nIsCancellationRequested:{0}", cts.IsCancellationRequested);
}
上面的代码定义了一个 Test() 方法,在方法内部,首先定义了一个 CancellationTokenSource 对象,该退出令牌源内部创建了一个取消令牌属性 Token ;接下来,使用 TaskFacory 任务工厂创建了 3 个并行任务,并把这个任务存入 List<Task> 列表对象中,在任务开始后,马上迭代 tasks 列表,通过同步获取每个任务的执行 Result 结果,在取消令牌没有收到取消通知的时候,任务将正常的执行下去,在所有任务都执行完成后,将 3 个请求结果输出到控制台中,同时销毁任务释放线程资源;最后,执行 cts.Cancel()取消令牌并释放资源,最后一句代码将输出令牌的状态。
1.2 执行程序,输出结果
通过上面的输出接口,可以看出,红色部分是模拟请求,这个请求时多线程进行的,Post 和 Love 交替出现,是因为在程序中通过线程休眠的方式模拟网络阻塞过程,蓝色为合并结果部分,可以看到,虽然“文章信息”已经加载完成,但是因为 Post 和 Love 还在请求中,由于取消令牌未收到退出通知,所以合并结果会等待信号,在所有线程都执行完成后,通过 cts.Cancel() 通知令牌取消,所有事件执行完成,控制台打印结果黄色部分为令牌状态,显示为 True ,令牌已取消。
2. 对长时间阻塞调用的异步取消令牌应用
在某些场景中,我们需要请求外部的第三方资源,比如请求天气预报信息;但是,由于网络等原因,可能会造成长时间的等待以致业务超时退出,这种情况可以使用 CancellationToken 来进行优化,但请求超过指定时长后退出,而不必针对每个 HttpClient 进行单独的超时设置
2.1 获取天气预报
public async static Task GetToday()
{
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(3000);
HttpClient client = new HttpClient();
var res = await client.GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts.Token);
var result = await res.Content.ReadAsStringAsync();
Console.WriteLine(result);
cts.Dispose();
client.Dispose();
}
在上面的代码中,首先定义了一个 CancellationTokenSource 对象,然后马上发起了一个 HttpClient 的 GetAsync 请求(注意,这种使用 HttpClient 的方式是不正确的,详见我的博客 HttpClient的演进和避坑 ;在 GetAsync 请求中传入了一个取消令牌,然后立即发起了退出请求 Console.WriteLine(result); 不管 3 秒后请求是否返回,都将取消令牌等待信号,最后输出结果释放资源
- 注意:如果是因为取消令牌退出引起请求中断,将会抛出任务取消的异常 TaskCanceledException
- 执行程序输出结果
3. CancellationToken 的链式反应
可以使用创建一组令牌,通过链接各个令牌,使其建立通知关联,当 CancellationToken 链中的某个令牌收到取消通知的时候,由链式中创建出来的 CancellationToken 令牌也将同时取消
3.1 创建链式测试代码
public async static Task Test()
{
CancellationTokenSource cts1 = new CancellationTokenSource();
CancellationTokenSource cts2 = new CancellationTokenSource();
var cts3 = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);
cts1.Token.Register(() =>
{
Console.WriteLine("cts1 Canceling");
});
cts2.Token.Register(() =>
{
Console.WriteLine("cts2 Canceling");
});
cts2.CancelAfter(1000);
cts3.Token.Register(() =>
{
Console.WriteLine("root Canceling");
});
var res = await new HttpClient().GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts1.Token);
var result = await res.Content.ReadAsStringAsync();
Console.WriteLine("cts1:{0}", result);
var res2 = await new HttpClient().GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts2.Token);
var result2 = await res2.Content.ReadAsStringAsync();
Console.WriteLine("cts2:{0}", result2);
var res3 = await new HttpClient().GetAsync("http://www.weather.com.cn/data/sk/101110101.html", cts3.Token);
var result3 = await res2.Content.ReadAsStringAsync();
Console.WriteLine("cts3:{0}", result3);
}
上面的代码定义了 3 个 CancellationTokenSource ,分别是 cts1,cts2,cts3,每个 CancellationTokenSource 分别注册了 Register 取消回调委托,然后,使用 HttpClient 发起 3 组网络请求;其中,设置 cts2 在请求开始 1秒 后退出,预期结果为:当 cts2 退出后,由于 cts3 是使用 CreateLinkedTokenSource(cts1.Token, cts2.Token) 创建出来的,所以 cts3 应该也会被取消,实际上,无论 cts1/cts2 哪个令牌取消,cts3 都会被取消
3.2 执行程序,输出结果
从上图可以看到,红色部分输出结果是:首先 cts2 取消,接着产生了链式反应导致 cts3 也跟着取消,蓝色部分为 cts1 的正常请求结果,最后输出了任务退出的异常信息
4. CancellationToken 令牌取消的三种方式
CancellationToken 定义了三种不同的取消方法,分别是 Cancel(),CancelAfter(),Dispose();这三种方式都代表了不同的行为方式
4.1 演示取消动作
public static void Test()
{
CancellationTokenSource cts1 = new CancellationTokenSource();
cts1.Token.Register(() =>
{
Console.WriteLine("\ncts1 ThreadId: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
});
cts1.Cancel();
Console.WriteLine("cts1 State:{0}", cts1.IsCancellationRequested);
CancellationTokenSource cts2 = new CancellationTokenSource();
cts2.Token.Register(() =>
{
Console.WriteLine("\ncts2 ThreadId: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
});
cts2.CancelAfter(500);
System.Threading.Thread.Sleep(1000);
Console.WriteLine("cts2 State:{0}", cts2.IsCancellationRequested);
CancellationTokenSource cts3 = new CancellationTokenSource();
cts3.Token.Register(() =>
{
Console.WriteLine("\ncts3 ThreadId: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
});
cts3.Dispose();
Console.WriteLine("\ncts3 State:{0}", cts3.IsCancellationRequested);
}
4.2 执行程序,输出结果如下
上面的代码定义了 3 个 CancellationTokenSource,分别是 cts1/cts2/cts3;分别执行了 3 中不同的取消令牌的方式,并在取消回调委托中输出线程ID,从输出接口中看出,当程序执行 cts1.Cancel() 方法后,取消令牌立即执行了回调委托,并输出线程ID为:1;cts2.CancelAfter(500) 表示 500ms 后取消,为了获得令牌状态,这里使线程休眠了 1000ms,而 cts3 则直接调用了 Dispose() 方法,从输出结果看出,cts1 运行在和 Main 方法在同一个线程上,线程 ID 都为 1,而 cts2 由于使用了延迟取消,导致其在内部新创建了一个线程,其线程 ID 为 4;最后,cts3由于直接调用了 Dispose() 方法,但是其 IsCancellationRequested 的值为 False,表示未取消,而输出结果也表明,没有执行回调委托
结束语
- 通过本文,我们学习到了如何在不同的应用场景下使用 CancellationToken
- 掌握了合并请求、中断请求、链式反应 三种使用方式
- 最后还了解到三种不同的取消令牌方式,知道了各种不同取消方式的区别