Sunday, February 20, 2011

Changes to the Rx API

Readers of the Reactive Extensions series on this blog will have noticed recently that not all of the code always works* if you download the Rx assemblies and then copy the code off the blog. This is due to the quite frequent changes to the API. I personally have 7 different versions of the libraries and I don’t think I have downloaded them all as they have been released! Also the blog post were started all the way back in May 2010 so it is fair that there has been some movement in the API

*If you get the code from the http://code.google.com/p/rx-samples/source/checkout or as a zip file then it will work, as the correct version of the assembly is included.

For fun I thought I would try to exercise my LINQ skills and write a quick diff tool so I can see what on the public API is actually changing on me. I threw this class together in LinqPad

public class AssemblyDiff
{
    private readonly string _oldAssemblyPath;
    private readonly string _newAssemblyPath;
    
    public AssemblyDiff(string oldAssemblyPath, string newAssemblyPath)
    {
        _oldAssemblyPath = oldAssemblyPath;
        _newAssemblyPath = newAssemblyPath;
    }
    
    public IEnumerable<String> NewMethodNames()
    {
        return MethodNameDelta(GetDeclaredMethods(_newAssemblyPath), GetDeclaredMethods(_oldAssemblyPath));
    }
    
    public IEnumerable<String> DeprecatedMethodNames()
    {
        return MethodNameDelta(GetDeclaredMethods(_oldAssemblyPath), GetDeclaredMethods(_newAssemblyPath));
    }
    
    public static IEnumerable<String> MethodNameDelta(IEnumerable<MethodInfo> original, IEnumerable<MethodInfo> modified)
    {
        return from methodName in original.Select(MethodName).Except(modified.Select(MethodName))
                    orderby methodName
                    select methodName;
    }
    public IEnumerable<MethodInfo> NewMethods()
    {
        var oldMethods = GetDeclaredMethods(_oldAssemblyPath);
        var currentMethods = GetDeclaredMethods(_newAssemblyPath);
        
        return MethodDelta(oldMethods, currentMethods);
    }
    
    public IEnumerable<MethodInfo> DeprecatedMethods()
    {
        var oldMethods = GetDeclaredMethods(_oldAssemblyPath);
        var currentMethods = GetDeclaredMethods(_newAssemblyPath);
        
        return MethodDelta(currentMethods, oldMethods);
    }
    
    public static IEnumerable<MethodInfo> MethodDelta(IEnumerable<MethodInfo> original, IEnumerable<MethodInfo> changed)
    {
        var existingTypes = original.Select(m => m.ReflectedType.FullName)
                                        .Distinct()
                                        .ToList();
        
        return from method in changed.Except(original, new MethodSignatureComparer())
                where existingTypes.Contains(method.ReflectedType.FullName)
                orderby method.ReflectedType.Name, method.Name
                select method;
    }
    
    public IEnumerable<Type> NewTypes()
    {
        var currentTypes = GetTypes(_newAssemblyPath);
        var oldTypes = GetTypes(_oldAssemblyPath);
        
        return from type in currentTypes
                where !oldTypes.Select (t => t.FullName).Contains(type.FullName)
                select type;
    }
    
    public IEnumerable<Type> DeprecatedTypes()
    {
        var currentTypes = GetTypes(_newAssemblyPath);
        var oldTypes = GetTypes(_oldAssemblyPath);
        
        return from type in oldTypes
                where !currentTypes.Select (t => t.FullName).Contains(type.FullName)
                select type;
    }
    
    private static IEnumerable<MethodInfo> GetAllMethods(string path)
    {
        return  from type in GetTypes(path)
                from method in type.GetMethods()
                where method.IsPublic
                select method;
    }
    
    private static IEnumerable<MethodInfo> GetDeclaredMethods(string path)
    {
        return GetAllMethods(path).Where(method => method.DeclaringType == method.ReflectedType);
    }
    
    private static IEnumerable<Type> GetTypes(string path)
    {
        return  from file in Directory.EnumerateFiles(path, "*.dll")
                from module in Assembly.LoadFrom(file).GetModules()
                from type in module.GetTypes()
                where type.IsPublic
                select type;
    }
    
    private static string MethodName(MethodInfo m)
    {
        return string.Format("{0}.{1}", m.ReflectedType.Name, m.Name);
    }
    
    public static string MethodSignature(MethodInfo m)
    {
        //return m.ToString();
        var ps = m.GetParameters();
        var args = ps.Select(p=>ParameterSignature(p, ps.Length));
        var argsDemlimted = string.Join(",", args);
        
        return string.Format("{0} {1}.{2}({3})", m.ReturnType.Name, m.ReflectedType.Name, m.Name, argsDemlimted);
    }
        
    private static string ParameterSignature(ParameterInfo parameter, int parameterCount)
    {
        var modifier = "";//out/ref/params/
        var defaultValue = "";
        if(parameter.IsOut) modifier = "out ";
        if(parameter.IsOptional)
        {
            modifier = "optional ";
            defaultValue = parameter.DefaultValue.ToString();
        }
        if(parameter.IsRetval) modifier += "isretval ";
        if(parameter.IsIn) modifier += "IsIn ";
        if(parameter.IsLcid) modifier += "IsLcid ";
        if(parameter.Position== parameterCount-1 && parameter.ParameterType.IsArray)
        {
            modifier = "params ";
        }
        return string.Format("{0}{1}{2}", modifier,parameter.Name, defaultValue);
    }
    
    private class MethodSignatureComparer : IEqualityComparer<MethodInfo>
    {
        public bool Equals(MethodInfo lhs, MethodInfo rhs)
        {                
            return string.Equals(lhs.ToString(), rhs.ToString());
        }
        
        public int GetHashCode(MethodInfo method)
        {
            return method.ToString().GetHashCode();
        }
    }
}

and then I used it like this

var old =         @"C:\Program Files\Microsoft Cloud Programmability\Reactive Extensions\v1.0.2787.0\Net4";
var current =     @"C:\Program Files\Microsoft Cloud Programmability\Reactive Extensions\v1.0.2838.0\Net4";

var dllDiff = new AssemblyDiff(old, current);

"New Methods".Dump();
dllDiff.NewMethodNames().Dump();
"Deprecated Methods".Dump();
dllDiff.DeprecatedMethodNames().Dump();
"New Types".Dump();
dllDiff.NewTypes().Select (t => t.FullName).Dump();
"Deprecated Types".Dump();
dllDiff.DeprecatedTypes().Select (t => t.FullName).Dump();
"New overloads".Dump();
dllDiff.NewMethods().Select(AssemblyDiff.MethodSignature).Dump();
"Deprecated overloads".Dump();
dllDiff.DeprecatedMethods().Select(AssemblyDiff.MethodSignature).Dump();

and I get this neat output.

New Methods (7 Items)

  • ConnectableObservable`2.Connect
  • ConnectableObservable`2.Subscribe
  • Observable.GroupJoin
  • Observable.Multicast
  • Observable.Window
  • Qbservable.GroupJoin
  • Qbservable.Window

Deprecated Methods (6 Items)

  • ConnectableObservable`1.Connect
  • ConnectableObservable`1.Subscribe
  • Observable.Prune
  • Observable.Replay
  • Qbservable.Prune
  • Qbservable.Replay

New Types (1 Item)

  • System.Collections.Generic.ConnectableObservable`2

Deprecated Types (0 Items)

New Overloads (30 Items)

  • IEnumerable`1 EnumerableEx.Generate(initialState,condition,iterate,resultSelector)
  • IObservable`1 Observable.BufferWithTime(source,timeSpan,timeShift,scheduler)
  • IObservable`1 Observable.BufferWithTime(source,timeSpan,scheduler)
  • IObservable`1 Observable.BufferWithTime(source,timeSpan,timeShift)
  • IObservable`1 Observable.BufferWithTime(source,timeSpan)
  • IObservable`1 Observable.BufferWithTimeOrCount(source,timeSpan,count,scheduler)
  • IObservable`1 Observable.BufferWithTimeOrCount(source,timeSpan,count)
  • IObservable`1 Observable.GroupJoin(left,right,leftDurationSelector,rightDurationSelector,resultSelector)
  • IObservable`1 Observable.If(condition,thenSource)
  • IObservable`1 Observable.Join(left,right,leftDurationSelector,rightDurationSelector,resultSelector)
  • IConnectableObservable`1 Observable.Multicast(source,subject)
  • IObservable`1 Observable.Publish(source,subject)
  • IObservable`1 Observable.Publish(source,subject,selector)
  • IObservable`1 Observable.Window(source,windowOpenings,windowClosingSelector)
  • IObservable`1 Observable.Window(source,windowClosingSelector,scheduler)
  • IObservable`1 Observable.Window(source,windowClosingSelector)
  • IQbservable`1 Qbservable.BufferWithTime(source,timeSpan,timeShift,scheduler)
  • IQbservable`1 Qbservable.BufferWithTime(source,timeSpan,scheduler)
  • IQbservable`1 Qbservable.BufferWithTime(source,timeSpan,timeShift)
  • IQbservable`1 Qbservable.BufferWithTime(source,timeSpan)
  • IQbservable`1 Qbservable.BufferWithTimeOrCount(source,timeSpan,count,scheduler)
  • IQbservable`1 Qbservable.BufferWithTimeOrCount(source,timeSpan,count)
  • IQbservable`1 Qbservable.GroupJoin(left,right,leftDurationSelector,rightDurationSelector,resultSelector)
  • IQbservable`1 Qbservable.If(provider,condition,thenSource)
  • IQbservable`1 Qbservable.Join(left,right,leftDurationSelector,rightDurationSelector,resultSelector)
  • IQbservable`1 Qbservable.Publish(source,subject,selector)
  • IQbservable`1 Qbservable.Publish(source,subject)
  • IQbservable`1 Qbservable.Window(source,windowOpenings,windowClosingSelector)
  • IQbservable`1 Qbservable.Window(source,windowClosingSelector,scheduler)
  • IQbservable`1 Qbservable.Window(source,windowClosingSelector)

Deprecated overloads (71 Items)

  • IEnumerable`1 EnumerableEx.Generate(initialState,condition,resultSelector,iterate)
  • IEnumerable`1 EnumerableEx.Generate(function)
  • IEnumerable`1 EnumerableEx.Generate(initialState,resultSelector,iterate)
  • IEnumerable`1 EnumerableEx.Generate(initial,resultSelector,iterate)
  • IEnumerable`1 EnumerableEx.Generate(initial,condition,resultSelector,iterate)
  • IObservable`1 Observable.BufferWithTime(source,timeSpan,timeShift,scheduler)
  • IObservable`1 Observable.BufferWithTime(source,timeSpan,scheduler)
  • IObservable`1 Observable.BufferWithTime(source,timeSpan,timeShift)
  • IObservable`1 Observable.BufferWithTime(source,timeSpan)
  • IObservable`1 Observable.BufferWithTimeOrCount(source,timeSpan,count,scheduler)
  • IObservable`1 Observable.BufferWithTimeOrCount(source,timeSpan,count)
  • IConnectableObservable`1 Observable.Prune(source)
  • IConnectableObservable`1 Observable.Prune(source,scheduler)
  • IObservable`1 Observable.Prune(source,selector)
  • IObservable`1 Observable.Prune(source,selector,scheduler)
  • IObservable`1 Observable.Publish(source1,source2,selector)
  • IObservable`1 Observable.Publish(source1,source2,selector,scheduler)
  • IObservable`1 Observable.Publish(source1,source2,source3,selector)
  • IObservable`1 Observable.Publish(source1,source2,source3,selector,scheduler)
  • IObservable`1 Observable.Publish(source1,source2,source3,source4,selector)
  • IObservable`1 Observable.Publish(source1,source2,source3,source4,selector,scheduler)
  • IConnectableObservable`1 Observable.Publish(source,initialValue)
  • IConnectableObservable`1 Observable.Publish(source,initialValue,scheduler)
  • IObservable`1 Observable.Publish(source,selector,initialValue)
  • IObservable`1 Observable.Publish(source,selector,initialValue,scheduler)
  • IConnectableObservable`1 Observable.Publish(source)
  • IConnectableObservable`1 Observable.Publish(source,scheduler)
  • IObservable`1 Observable.Publish(source,selector)
  • IObservable`1 Observable.Publish(source,selector,scheduler)
  • IConnectableObservable`1 Observable.Replay(source)
  • IConnectableObservable`1 Observable.Replay(source,scheduler)
  • IObservable`1 Observable.Replay(source,selector)
  • IObservable`1 Observable.Replay(source,selector,scheduler)
  • IConnectableObservable`1 Observable.Replay(source,window)
  • IObservable`1 Observable.Replay(source,selector,window)
  • IConnectableObservable`1 Observable.Replay(source,window,scheduler)
  • IObservable`1 Observable.Replay(source,selector,window,scheduler)
  • IConnectableObservable`1 Observable.Replay(source,bufferSize,scheduler)
  • IObservable`1 Observable.Replay(source,selector,bufferSize,scheduler)
  • IConnectableObservable`1 Observable.Replay(source,bufferSize)
  • IObservable`1 Observable.Replay(source,selector,bufferSize)
  • IConnectableObservable`1 Observable.Replay(source,bufferSize,window)
  • IObservable`1 Observable.Replay(source,selector,bufferSize,window)
  • IConnectableObservable`1 Observable.Replay(source,bufferSize,window,scheduler)
  • IObservable`1 Observable.Replay(source,selector,bufferSize,window,scheduler)
  • IQbservable`1 Qbservable.BufferWithTime(source,timeSpan,timeShift,scheduler)
  • IQbservable`1 Qbservable.BufferWithTime(source,timeSpan,scheduler)
  • IQbservable`1 Qbservable.BufferWithTime(source,timeSpan,timeShift)
  • IQbservable`1 Qbservable.BufferWithTime(source,timeSpan)
  • IQbservable`1 Qbservable.BufferWithTimeOrCount(source,timeSpan,count,scheduler)
  • IQbservable`1 Qbservable.BufferWithTimeOrCount(source,timeSpan,count)
  • IQbservable`1 Qbservable.Prune(source,selector,scheduler)
  • IQbservable`1 Qbservable.Prune(source,selector)
  • IQbservable`1 Qbservable.Publish(source,selector,initialValue)
  • IQbservable`1 Qbservable.Publish(source,selector,scheduler)
  • IQbservable`1 Qbservable.Publish(source,selector,initialValue,scheduler)
  • IQbservable`1 Qbservable.Publish(source,selector)
  • IQbservable`1 Qbservable.Publish(source1,source2,selector)
  • IQbservable`1 Qbservable.Publish(source1,source2,selector,scheduler)
  • IQbservable`1 Qbservable.Publish(source1,source2,source3,selector)
  • IQbservable`1 Qbservable.Publish(source1,source2,source3,selector,scheduler)
  • IQbservable`1 Qbservable.Publish(source1,source2,source3,source4,selector)
  • IQbservable`1 Qbservable.Publish(source1,source2,source3,source4,selector,scheduler)
  • IQbservable`1 Qbservable.Replay(source,selector,bufferSize)
  • IQbservable`1 Qbservable.Replay(source,selector,bufferSize,window)
  • IQbservable`1 Qbservable.Replay(source,selector,bufferSize,window,scheduler)
  • IQbservable`1 Qbservable.Replay(source,selector)
  • IQbservable`1 Qbservable.Replay(source,selector,scheduler)
  • IQbservable`1 Qbservable.Replay(source,selector,window)
  • IQbservable`1 Qbservable.Replay(source,selector,window,scheduler)
  • IQbservable`1 Qbservable.Replay(source,selector,bufferSize,scheduler)

From memory Generate has been a constant source of change which has confused some readers that are using different versions of the library to what the Part 2 post was done with. This diff script  goes to show that it is still undergoing changes

Smile

Links:

Reactive Extensions for .NET an Introduction

DevLabs: Reactive Extensions for .NET (Rx)

1 comment:

Anonymous said...

Thanks a bunch for this tool Lee