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)

Sunday, February 13, 2011

Silverlight testing

I am putting this out there to see if I can get some traction with other Test Driven Silverlight coders out there. If you are one of these people you will know of the strife your day-to-day coding. For those who don't know what I mean these are the three options a Silverlight developer has with regards to test driven coding:

1) Use the Silverlight Unit Test Framework. The problem with this is that you lose any integrated development support, it is amazingly slow (in the area of 1-5 tests per second), and doesn't have any useful build tool support (coverage, TFS, TeamCity). Massive Fail.

2) Cross compile to .NET 4 all of your Models, ViewModels, Controllers, Modules, Presenters (i.e. everything that is not a View or a Control). Now write unit tests against this project. This means you get back to fast tests (100s tests per second) but take a hit on compiling twice and managing the project linking and just having twice as many projects floating around.

3) What I imagine as the most popular option, just don't write any tests.

Looking at what most of the requests are for, tells me most people are using Silverlight for Marketing websites to stream rich content. Business applications have yet to stake any dominance. What I am hoping that anyone reading this will if they feel my pain, just go to this link and vote. It seems that these polls really have an effect; DataTemplates appear to be part of SL 5 due to massive demand. I am hoping that Microsoft can focus on getting the underlying framework right before they go off and give us a 3D-multitouch-proximity aware API :)

http://dotnet.uservoice.com/forums/4325-silverlight-feature-suggestions/suggestions/313397-unit-testing-integrated-in-visual-studio-and-msbui?ref=title