RCode

a software development blog by Bojan Resnik

Archive for the ‘C#’ Category

Type Aliases in C#

Posted by Bojan Resnik on August 14, 2009

I was maintaining a piece of C# code today, which made heavy use of a class with several generic parameters. The class has several static methods and nested types, so its full signature was all over the place:

private Graph<int, object, List<int>> CreateGraph()
{
    var result = new Graph<int, object, List<int>>();

    // Code to populate the graph...

    return result;
}

private void Traverse(Graph<int, object, List<int>> graph)
{
    foreach (var node in graph.Roots)
        Traverse(node);
}

private void Traverse(Graph<int,object,List<int>>.Node node)
{
    // Do something with node...

    foreach (var child in node.Children)
        Traverse(child);
}

This is just a sample of the code – almost every method in the class had Graph<int, object, List<int>> somewhere in it. The code would be much easier to maintain if it didn’t depend on this full signature so heavily. Fortunately, C# provides a convenient way to solve this with type aliases:

using DocumentGraph = Graph<int, object, List<int>>;

The offending code can now use DocumentGraph:

private DocumentGraph CreateGraph()
{
    var result = new DocumentGraph();

    // Code to populate the graph...

    return result;
}

private void Traverse(DocumentGraph graph)
{
    foreach (var node in graph.Roots)
        Traverse(node);
}

private void Traverse(DocumentGraph.Node node)
{
    // Do something with node...

    foreach (var child in node.Children)
        Traverse(child);
}

The type alias directive can be specified at file or namespace scope – it cannot be specified in a class or method.

kick it on DotNetKicks.comShout it

Posted in .NET, C# | 1 Comment »

C# lambda and foreach variable

Posted by Bojan Resnik on June 17, 2009

Can you guess what the output of the following program is?

using System;
using System.Collections.Generic;

namespace Lambda
{
    class Program
    {
        static void Main(string[] args)
        {
            var strings = new[] {"a", "b", "c"};
            var actions = CreateActions(strings);
            actions.ForEach(f => f());
        }
        
        private static List<Action> CreateActions(IEnumerable<string> strings)
        {
            var actions = new List<Action>();
            
            foreach (var s in strings)
                actions.Add( () => Console.WriteLine(s) );
            
            return actions;
        }
    }
}

I expected to see all three strings (a, b and c), but instead I got this:
First output

While analyzing the IL code with Reflector I learned a thing or two about lambdas in C#.
When a lambda is encountered by the compiler, it generates a class which has a field for each local variable used by the lambda. In this case, the generated class would look like this in C#:

private sealed class <>c_DisplayClass3
{
    public string s;
    public void <Main>b_0()
    { Console.WriteLine(this.s); }
}

Of course, the above is not valid C# due to invalid characters in identifiers, but is pretty much what is produced in the IL.
So far so good – no real surprises here. However, look at this code which is my translation from IL to C# of the CreateActions method:

private static List<Action> CreateActions(IEnumerable<string> strings)
{
    List<Action> actions = new List<Action>();
    using (IEnumerator<string> enumerator = strings.GetEnumerator())
    {
        // Note 1: The lambda is created outside of the loop
        <>c_DisplayClass3 lambda = new <>c_DisplayClass3();

        string s;
        while (enumerator.MoveNext())
        {
            s = enumerator.Current;

            // Note 2: The instance field is reassigned each time
            lambda.s = s;
            actions.Add(lambda);
        }
    }
    return actions;
}

The reason for the strange output is clear now – the compiler did not create a lambda object in each iteration but rather reused the existing one each time. After the loop, all items in the actions list are actually the same instance and their s field is the last element of the collection. Apparently, C# compiler creates a lambda instance immediately before the local variable it uses. So, in order to force it to create a new lambda for each iteration, we need to introduce a new local variable in the loop:

private static List<Action> CreateActions(IEnumerable<string> strings)
{
    var actions = new List<Action>();
    
    foreach (var s in strings)
    {
        // This variable will be used in the lambda
        var lambda_param = s;
        actions.Add( () => Console.WriteLine(lambda_param) );
    }
    
    return actions;
}

With this modification the program now produces this output:
Second output

kick it on DotNetKicks.comShout it

Posted in .NET, C# | Tagged: , , | 33 Comments »

Installing the same managed addin in 32bit and 64bit Autodesk Inventor

Posted by Bojan Resnik on May 21, 2009

I have an addin for AutoDesk Inventor 2009, written in C# and a setup project to go with it. In order to register the addin with Inventor, I needed to register the assembly with “/codebase” switch, so I made a custom installer action:

[RunInstaller(true)]
public partial class AddinInstaller : Installer
{
    public override void Install(System.Collections.IDictionary stateSaver)
    {
        base.Install(stateSaver);
        RegistrationServices regsrv = new RegistrationServices();
        regsrv.RegisterAssembly(GetType().Assembly, AssemblyRegistrationFlags.SetCodeBase);
    }

    public override void Uninstall(System.Collections.IDictionary savedState)
    {
        base.Uninstall(savedState);

        RegistrationServices regsrv = new RegistrationServices();
        regsrv.UnregisterAssembly(GetType().Assembly);
    }
}

The actual registration code run by the registration service modifies some registry keys under HKEY_CLASSES_ROOT. This worked fine on Windows XP, but when run on my Vista 64, the setup completed successfully, but failed to register the addin with Inventor. The reason became apparent when I realized that my Inventor installation is also 64 bit – which means it looks for the keys using the 64bit view of the registry, while the installer action runs with the 32bit view.

The code itself is all C#, compiled as “Any CPU”, so it should have no problems running unmodified with both 64 and 32 bit Inventors. The only problem is how to run the installer as 64 bit. I also wanted to allow the same MSI to be used on all platforms, so compiling multiple versions was not an option. And searching the net, I got the impression that it would not solve the problem anyway – at least not without some other tools.

The solution I came up with is simpler: instead of using RegistrationServices object, I can manually execute the RegAsm.exe tool to register the assembly. On my Vista 64, there are two versions of this tool:

  • C:\Windows\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe
  • C:\Windows\Microsoft.NET\Framework64\v2.0.50727\RegAsm.exe

The directories might not be the same on other machines, of course. Fortunately, .NET provides the RuntimeEnvironment.GetRuntimeDirectory() static method which returns the folder of the common language runtime. On my machine, it returns C:\Windows\Microsoft.NET\Framework\v2.0.50727\ inside the installer action. We can use this, combined with RuntimeEnvironment.GetSystemVersion() to construct the paths to 32bit and 64bit versions of the tool:

[RunInstaller(true)]
public partial class AddInInstaller : Installer 
{
    public AddInInstaller() 
    { InitializeComponent(); }

    public override void Install(IDictionary stateSaver) 
    {
        base.Install(stateSaver);
        RegAsm("/codebase");
    }

    public override void Uninstall(IDictionary savedState) 
    {
        RegAsm("/u");
        base.Uninstall(savedState);
    }

    private static void RegAsm(string parameters)
    {
        // RuntimeEnvironment.GetRuntimeDirectory() returns something like
        //    C:\Windows\Microsoft.NET\Framework64\v2.0.50727\
        // We need to get to the
        //    C:\Windows\Microsoft.NET
        // part in order to create 32 and 64 bit paths
        var net_base = Path.GetFullPath(Path.Combine(RuntimeEnvironment.GetRuntimeDirectory(), @"..\.."));
                    
        // Create paths to 32bit and 64bit versions of regasm.exe
        var to_exec = new[]
        {
            string.Concat(net_base, "\\Framework\\", RuntimeEnvironment.GetSystemVersion(), "\\regasm.exe"), 
            string.Concat(net_base, "\\Framework64\\", RuntimeEnvironment.GetSystemVersion(), "\\regasm.exe")
        };

        var dll_path = Assembly.GetExecutingAssembly().Location;
 
        foreach (var path in to_exec)
        {
            // Skip the executables that do not exist
            // This most likely happens on a 32bit machine when processing the path to 64bit regasm
            if ( !File.Exists(path) )
                continue;
                
            var process = new Process
            {
                StartInfo =
                {
                    CreateNoWindow = true,
                    ErrorDialog = false,
                    UseShellExecute = false,
                    FileName = path,
                    Arguments = string.Format("\"{0}\" {1}", dll_path, parameters)
                }
            };
            
            using (process)
            {
                process.Start();
                process.WaitForExit();
            }
        }
    }
}

Posted in .NET, C#, Inventor | 8 Comments »