协变与逆变

这个概念很多年前就知道,但是总是学了忘学了忘,还是因为没有从本质上了解它,没有总结好。

首先要定下来一个变字:
Variance refers to how subtyping between more complex types relates to subtyping between their components.。 举个例子就是整个集合(接口,不是类)的继承性和集合中当个元素的继承性之间的关系,或者是delegate(Function和Action这些)和它的入参出参之间的继承关系的传递。
CoVariance就是协(同)变, 所以是component有父子类关系的,连带着把复杂对象也提携成有父子类继承关系了。比如IEnumerable<Cat>是 IEnumerable<Animal>的子类型。于是就可以安全地把子类对象付给父类变量了(变就是允许跨父子关系的变量赋值)。
ContraVariance是逆变,就是Component有父子关系,连带着把复杂对象变成了有子父关系的(反过来的)。例子就是 Action<Cat> 是 Action<Animal> 的父类型。所以可以把Action<Animal>类型的delegate实例给Action<Cat>类型的delegate变量赋值,为下一步的调用做准备。
无论是IEnumerable接口还是Action delegate,或者是这里的泛型类的例子,component的类型都是作为泛型参数指定的,所以c#在指定泛型类参数的添加了一个in/out的泛型参数modifier,专门用来指明这个泛型参数的给它的父结构带来的变化(Variance)方向是协变还是逆变。in是逆变,out是协变
 namespace System.Collections.Generic下的
public interface IEnumerable<out T> : IEnumerable
就可以理解为参数T给IEnumerable<T>带来了协变。
而namespace system下的
public delegate void Action<in T>(T obj);
则说明参数T给Action<T>带来了逆变。实例见上文。
另外要注意,
1. 变只是针对接口,方法,泛型类定义里的参数,从来就不是针对具体的Collection类。
2. 集合接口中也只适用只有只读属性的接口,比如IEnumerable<out T>,不包括IList<T>,因为IList带有非只读方法,可以增加集合元素,所以不支持变。

IList<Animal> animals;
List<Dog> dogs = new List<Dog>();
animals =dogs; //compile error, no CoVariance for IList<T>
animals = dogs.ToArray(); //compile pass,dog array is IList<Dog>, there is no varance here.
animals.Add(new Dog()); //runtime error: ‘Collection was of a fixed size.’ internally, Array doesn’t support Add() as List class do.

最后两行是另一个话题,即Array的实现,根据 https://msdn.microsoft.com/en-us/library/system.array(v=vs.110).aspx
Unlike the classes in the System.Collections namespaces, Array has a fixed capacity. To increase the capacity, you must create a new Array object with the required capacity, copy the elements from the old Array object to the new one, and delete the old Array.(这也是List<T>类的实现)
Starting with the .NET Framework 2.0, the Array class implements the System.Collections.Generic.IList<T>System.Collections.Generic.ICollection<T>, and System.Collections.Generic.IEnumerable<T> generic interfaces. The implementations are provided to arrays at run time, and as a result, the generic interfaces do not appear in the declaration syntax for the Array class. In addition, there are no reference topics for interface members that are accessible only by casting an array to the generic interface type (explicit interface implementations). The key thing to be aware of when you cast an array to one of these interfaces is that members which add, insert, or remove elements throw NotSupportedException.

不仅在运行时实现了IList<T>, Array类还静态实现了IList接口,代码是这样实现的:

int IList.Add(object value)
{
throw newNotSupportedException(Environment.GetResourceString(“NotSupported_FixedSizeCollection”));
}
所以System.Array类只是提供了模版代码给[]构造器用,很多接口并没有真正实现。违反了LSP.

async方法fn直接.result会堵塞无法结束的问题

 碰到过这个一个问题,fn是一个aysnc方法,在spotfire程序的UI线程上经过多层方法调用到的时候如果是直接写var a = fn().result就把spotfire卡死了。result也拿不出来(看不到结果a)

这实际是一个没有正确使用异步方法的问题。异步方法虽然可以同步调用(.result),但如果是UI线程里这么干,就出事了。async就要await调用,如果当前方法不是异步的(比如上层的接口定下来了,不过好像加不加async不影响接口实现),那就只能多包一层线程来同步调用。
下面第一篇文章解释了await关键字实际干了什么事(即SynchronizationContext.Current.post,post到UI线程的消息队列里),以及.result干了什么事情(同步执行)
下面这篇文章解释了SynchronizationContext在异步编程中的作用,特别是和ExecutionContext的差异(ExecutionContext是内部的,一般不用管)

所以使用await关键字调用async方法不会有问题。因为UI线程没有阻塞,UI的消息队列还有机会处理Post来的http异步请求结束的消息。

但是fn.result会阻塞,因为是同步执行,所以UI的onClick方法根本没有结束,UI消息队列没有空来执行其它东西的能力(http异步请求的东西取回来了却结束不了)。
所以Three ways out:
1.使用await调用aysnc, 这是最优方案,因为UI click事件执行完全不需要等待http请求,消息队列没有空转着等待。
2. ConfigureAwait(false),这个算是workaround,UI click事件还是等http请求结束了才全部结束,而且中间还要利用一个别的线程来执行http请求结束存放变量的操作,然后再回到UI线程来结束整个click.这个过程中消息队列在白白等待着。
3.上面的task.run( ()=>fn()).result方法再起一个线程来启动fn,在不能改Library,又不能用await的情况下也是一种写法。
之所以建议Library中总是ConfigureAwait(false)就是怕有用户在使用当前Task或上层Task(不一定是这个Task(Awaitable)的直接调用)时,用.Result的方式”同步地使用异步方法”,而没有使用await(不是所有的上层调用都像UI的button click方法private async void Button_Click()都支持async.
关于async和Task的使用可以参考:
实际上async方法基本都需要返回一个Task,上述的async void Button_Click()是一个不好的pattern,   “fire-and-forget”机制, caller没有办法了解这个方法执行完成的情况或者抛出的错误。只有在最上层的UI event等系统方法才这么写。
我觉得其实是微软的UI Event代码带坏了大家,完全可以设计成必须要返回一个Task的机制,哪怕是Task<void>,也能保证后面的代码被suspend到这个task之后。如果自己写的代码是aysnc void,则因为没有GetAwaiter()(属于Task),则不能被await,结果就直接跑过去了,即使aysnc void方法里有下一层的await调用,因为上层没有等它执行完,这个await调用也不会suspend aysnc void方法后的代码执行,仅仅是系统看到await再开一个线程去跑而已。顺序关系需要GetAwaiter()串起来才能实现。

修复windows 7系统无法自动更新的问题

下载下面的小程序运行就好。

Reset Windows Update Components. Short link

<<http://go.microsoft.com/?linkid=9665683>>

This tool automates the most common fixes to issu?s with the Windows Update site.

Note this Fix it 50202 Solution has two modes Default and Aggressive.

You should run the Fix it solution in Default mode and determine if it resolves your problem with Windows Update before running it inAggressive mode.

The Update History section at <<http://windowsupdate.microsoft.com>> will be blank/empty after running the Fix It in AGGRESSIVEmode.