Expression Editor had a lot of improvements in Service Studio 5.0. You’ve probably already found most of them, but just in case you haven’t, here’s a list of some of them:

- The window is no longer modal so you can open several of them for comparison and copy/paste.

- Besides dragging from the scope tree, you can now also drag variables & actions set as functions from the eSpace tree.

- As you’re typing, the border of the text editor turns red when the expression is invalid, giving you immediate feedback when make a typo. Additionally, the case of variables and functions is automatically corrected to match its definition, so you have that extra degree of confidence that the TrueChange™ engine recognized what you just typed when you see it fix the case for you.

- You can now right click most elements in the scope tree and go to their definition:

imageimage imageimage image

 

- The completion list now includes the icons of the elements and tooltips about them:

image

- The filtering algorithm of the completion list is now smarter, and also matches word at the middle instead of just at the start:

image 

- It also detects acronyms, so if you open up the completion list by pressing Ctrl+Space and then type “ni” it automatically selects the “NullIdentifier()” built-in function. In addition, if you don’t have the completion list open yet and just type “ni” followed by Ctrl+Space, it automatically inserts “NullIdentifier()” if there’s no other match as good. This works also for NullDateTime, CurrDateTime, and other variations.

How cool is that?

Technorati Tags:

A little more than two years ago, shortly after the Agile Platform version 4.1 was released, the engineering team at OutSystems joined together and did a self review on what we could do to improve our ability to deliver faster and with better quality. We already knew that the developers that used the OutSystems Platform were much more productive than the ones using .NET or J2EE, but we wanted to raise the bar even higher, taking their productivity to the next level. The current version of Service Studio had a few Achilles’ heels that were being hard to get rid of, in part due to some compromises that had to be done in the past to enable us to get the first version out as soon as possible. That legacy was starting to slow down our ability to deliver constant innovation in the agile software development field, so, after a series of brainstorming sessions, we decided that we would re-engineer some parts of the platform in order to enable us to be even more agile, and prepare ourselves to the great features we wanted to build in the future. And this was the start of what would later be called the Butterfly project.

While most of the R&D began the work on the Antelope version (4.2), two teams were spawned to work on the Butterfly version (5.0). While the Blue Morphos team initiated a deep analysis on the features we would want to deliver on the long term, the Red Bull team started immediately the work on the platform re-engineering that would enable us to deliver those features. This team started off with just two developers (myself included), growing to four developers shortly after. A year later, while 4.2 was approaching completion, another team joined us to work on the 5.0 killer feature – Business Process Technology – already using the new codebase. At the end, most of the R&D was involved, and with the hard work of 14 Developers (3 of them fully dedicated to Quality Assurance), 3 Product Managers, our product delivery manager and of course the support from all the other parts of the company, from marketing to support, this journey finally came to an end, and I think that the result is a great product. I’m really proud of being involved in it since the start, and I feel like a mother that has just gave birth to its new-born child: it was though, but now that it’s over all that matters is the marvelous new baby :)

So, what did version 5.0 of the Agile Platform brought us? Aside from the Business Process Technology which by itself is a groundbreaking step on the history of agile development, we were able to deliver a few other big improvements:

  • Ridiculously faster load and save times. For example, a 5.2MB .oml file that took 75 seconds to open and 20 seconds to save in 4.2, now takes 8 seconds to open and 2,5 seconds to save in 5.0. That amounts to 89,3% and 87,5% speed improvements respectively!!! Find, Find Usages, Merge & Clone are also now significantly faster.
  • Most applications crash from time to time, making you loose your work. A few of them them save backups periodically and allow you to recover most of it, but many times you still loose a big chunk. Service Studio 5.0 is the only application that I know of that automatically heals itself in case of errors, rollbacking the tiny action that caused the error and allowing you to continue your work as if no crash has happened, without loosing any work at all.
  • The new improved TrueChange™ engine is now omnipresent, immediately informing the developer of his typos & mistakes by the form of toolips, visual cues for warnings and errors, drop-down suggestions for common operations and a message pane that refreshes in real-time. In addition to that, all the system is now much more strongly bound, and now you can really assemble the pieces of your application like a puzzle, without fear of things getting unbound and loosing the strong checking abilities. Refactoring is now so easy that your application will be truly built-to-change.
  • There’s a big focus on usability, and most pieces of the Service Studio workspace have been improved. There’s an new web screen editor with improved WYSIWYG editing, better zoom support, a big number of new drag&drop behaviors, support for copy/pasting everything, multi-level undo support, syntax highlighting in expressions, SQL, CSS & JavaScript, more information shown in the tools tree, eSpace tree & the add/remove references tree, etc…

There’s a lot more, so go and try the community edition, and let yourself be pleasantly surprised.

This is just the tip of the iceberg, as much of the potential of the new system is still not being used, so stay tuned for the next chapters. The next versions will be even more groundbreaking.

Technorati Tags:

I decided to give twitter a try. You can follow me on http://twitter.com/ovatsus.

Technorati Tags:
posted @ Saturday, May 16, 2009 1:32 PM | Feedback (0) | Filed Under [ Misc ]

You can get the PowerPoint slides and the demo code from here.

Technorati Tags: ,

Tomorrow I'll do a presentation about WPF in the Microsoft Dreamway event in Lisbon, at Centro Cultural de Belém.

My session will be Dev09 at 17:30. Check out the agenda.

Technorati Tags: ,

After you start to use extension methods and see all their benefits, you start thinking about how nice it would be to also have extension properties. When Scott Guthrie first announced them in New "Orcas" Language Feature: Extension Methods, someone immediately asked about extension properties. It just seems the feature is incomplete without both.

One particular area that would benefit greatly from extension properties would be WPF. Every time you define an attached property, you usually create a static getter and a static setter for it. If you could package that up in an extension property, attached properties would be much nicer to use.

There have been some hints that the C# language team is considering extension properties for the future, so maybe we'll get them in C# 4.0. But if you don't want to wait until 2010, you can have extension properties right now if you use F#. Check out Matthew Podwysocki's Object Oriented F# - Extension Everything.

Technorati Tags: , ,

Smashing Magazine has been lately posting some really wonderful photos in their site on their Monday Inspiration section:

35 Fantastic HDR Pictures

(Really) Stunning Pictures and Photos

Beautiful Black and White Photography

Let There Be Light: Light Paintings and Sculptures

45 Beautiful Motion Blur Photos

35 Beautiful Examples of Rain Photography

The also have some nice CSS & Photoshop tutorials really worth checking out.

Technorati Tags:

Updated on 2008/09/17: Fixed problem when skipping elements.
Updated on 2008/09/15: Fixed problem when trying to read missing attributes.

One of new features introduced in .NET 3.5 that I welcomed the most was LINQ to XML. The old DOM API was a bit clumsy to use, and the simple fact that you don't need owner documents any more makes the new XElement much more flexible and pleasant to work with than the old XmlElement.

Also new is an API for streaming XML output, XStreamingElement, that by using deferred execution gives you SAX-like performance on a DOM-like API. There's no streaming XML input API, though, so although you can now get away with not having to use XmlWriter any more, you'll still need to use XmlReader when you want good performance on large documents. During the LINQ to XML development, the XML Team considered such an API, but they decided not to do it for Orcas.

Fortunately, Ralf Lämmel proposed such an API in API-based XML streaming with FLWOR power and functional updates. I contacted him too ask if he could publicly release the code of his prototype, but he said he couldn't do it. Nevertheless, he kindly offered to help me develop a similar library myself, so with his help I implemented a small subset of the functionality he described in the paper. It took a while to get the corner cases right, but this is being used in a real-world scenario for some months now, so I think it's stable enough.

The interface is the following:

XElementReader(string path);
XElementReader(TextReader reader);
XElementReader(XmlReader reader);
XElementReader(Stream stream);
 
XName Name { get; }
string Value { get; }
 
XAttribute Attribute(XName name);
IEnumerable<XAttribute> Attributes();
 
XElementReader FirstElement();
XElementReader Element(XName name);
IEnumerable<XElementReader> Elements();
IEnumerable<XElementReader> Elements(XName name);
 
XElement ToXElement();

Although XElementReader looks a lot like a subset of XElement, you still have to remember that it's using a XmlReader underneath, so after you try to get to any child element, you have changed the reader position.

For example, if you have an element like <Root><A/><B/><A/><B/></Root> and call .Element("A") twice and then .Element("B") twice, the second call for B will return null. If you instead call .Elements("A") and then .Elements("B"), you'll get two A elements, but no B elements at all. So to do this right, you have iterate on .Elements() and check the .Name property to see if you're in an A or in a B element.

This read once nature sometimes also complicates debugging. To help with that, you can define XML_DEBUG_MODE to force XElementReader to use a XElement behind the covers instead of a XmlReader, so you can add watches freely without worrying about side effects. But remember to fully test with this conditional compilation symbol off.

Here's the full code:

using System;
using System.Collections.Generic;
using System.IO;
#if XML_DEBUG_MODE
using System.Linq;
#endif
using System.Xml;
using System.Xml.Linq;
 
public class XElementReader : IDisposable {
 
    private int targetDepth;
 
#if XML_DEBUG_MODE
 
        private readonly XElement elem;
 
        private XElementReader(XElement elem, int depth) {
            this.elem = elem;
            this.targetDepth = depth;
        }
 
        public XElementReader(string path) {
            this.elem = XElement.Load(path);
        }
 
        public XElementReader(TextReader reader) {
            this.elem = XElement.Load(reader);
        }
 
        public XElementReader(XmlReader reader) {
            this.elem = XElement.Load(reader);
        }
 
        public XElementReader(Stream stream) {
            this.elem = XElement.Load(XmlReader.Create(stream));
        }
 
        public void Close() {
            if (targetDepth != 0) {
                throw new InvalidOperationException("Only the outermost reader should be closed");
            }
        }
 
        public XName Name {
            get { return elem.Name; }
        }
 
        public string Value {
            get { return elem.Value; }
        }
 
        public XAttribute Attribute(XName name) {
            return elem.Attribute(name);
        }
 
        public IEnumerable<XAttribute> Attributes() {
            return elem.Attributes();
        }
 
        public XElementReader FirstElement() {            
            XElement subElem = elem.Elements().FirstOrDefault();            
            return subElem == null ? null : new XElementReader(subElem, targetDepth + 1);        
        }
 
        public XElementReader Element(XName name) {
            XElement subElem = elem.Element(name);
            return subElem == null ? null : new XElementReader(subElem, targetDepth + 1);
        }
 
        public IEnumerable<XElementReader> Elements() {
            return elem.Elements().Select(e => new XElementReader(e, targetDepth + 1));
        }
 
        public IEnumerable<XElementReader> Elements(XName name) {
            return elem.Elements(name).Select(e => new XElementReader(e, targetDepth + 1));
        }
 
        public XElement ToXElement() {
            return elem;
        }
 
#else
 
    private readonly XmlReader reader;
 
    private bool onlyAttributesWereRead = true;
 
    private XElementReader(XmlReader reader, int depth) {
        this.reader = reader;
        this.targetDepth = depth;
    }
 
    public XElementReader(string path) {
        this.reader = XmlReader.Create(path);
    }
 
    public XElementReader(TextReader reader) {
        this.reader = XmlReader.Create(reader);
    }
 
    public XElementReader(XmlReader reader) {
        this.reader = reader;
    }
 
    public XElementReader(Stream stream) {
        this.reader = XmlReader.Create(stream);
    }
 
    public void Close() {
        if (targetDepth != 0) {
            throw new InvalidOperationException("Only the outermost reader should be closed");
        }
        reader.Close();
    }
 
    public XName Name {
        get {
            if (!onlyAttributesWereRead) {
                throw new InvalidOperationException("This operation must be done before retrieving any element");
            }
 
            GoToDepth(targetDepth);
 
            return XName.Get(reader.LocalName, reader.NamespaceURI);
        }
    }
 
    public string Value {
        get {
            if (!onlyAttributesWereRead) {
                throw new InvalidOperationException("This operation must be done before retrieving any element");
            }
            onlyAttributesWereRead = false;
 
            GoToDepth(targetDepth);
 
            return reader.ReadElementContentAsString();
        }
    }
 
    public XAttribute Attribute(XName name) {
        if (!onlyAttributesWereRead) {
            throw new InvalidOperationException("This operation must be done before retrieving any element");
        }
 
        GoToDepth(targetDepth);
 
        var attribute = reader.GetAttribute(name.LocalName, name.NamespaceName);
        return attribute == null ? null : new XAttribute(name, attribute);
    }
 
    public IEnumerable<XAttribute> Attributes() {
        if (!onlyAttributesWereRead) {
            throw new InvalidOperationException("This operation must be done before retrieving any element");
        }
 
        GoToDepth(targetDepth);
 
        try {
            for (int i = 0; i < reader.AttributeCount; ++i) {
                reader.MoveToAttribute(i);
                yield return new XAttribute(reader.Name, reader.Value);
            }
        } finally {
            reader.MoveToElement();
        }
    }
 
    public XElementReader FirstElement() {
        onlyAttributesWereRead = false;
 
        if (!GoToDepth(targetDepth + 1)) {
            return null;
        } else {
            return new XElementReader(reader, targetDepth + 1);
        }
    }
 
    public XElementReader Element(XName name) {
        onlyAttributesWereRead = false;
 
        if (!GoToDepth(targetDepth + 1)) {
            return null;
        }
 
        if (XName.Get(reader.LocalName, reader.NamespaceURI) == name) {
            return new XElementReader(reader, targetDepth + 1);
        }
 
        if (reader.ReadToNextSibling(name.LocalName, name.NamespaceName)) {
            return new XElementReader(reader, targetDepth + 1);
        }
 
        return null;
    }
 
    public IEnumerable<XElementReader> Elements() {
        onlyAttributesWereRead = false;
 
        do {
            if (!GoToDepth(targetDepth + 1)) {
                yield break;
            }
            XElementReader innerReader = new XElementReader(reader, targetDepth + 1);
            yield return innerReader;
            if (innerReader.onlyAttributesWereRead) {
                reader.Skip();
            }
        } while (reader.Depth >= targetDepth + 1);
    }
 
    public IEnumerable<XElementReader> Elements(XName name) {
        onlyAttributesWereRead = false;
 
        do {
            if (!GoToDepth(targetDepth + 1)) {
                yield break;
            }
            if (XName.Get(reader.LocalName, reader.NamespaceURI) == name) {
                XElementReader innerReader = new XElementReader(reader, targetDepth + 1);
                yield return innerReader;
                if (innerReader.onlyAttributesWereRead) {
                    reader.Skip();
                }
            } else {
                if (!reader.ReadToNextSibling(name.LocalName, name.NamespaceName)) {
                    yield break;
                }
            }
        } while (reader.Depth >= targetDepth + 1);
    }
 
    public XElement ToXElement() {
        if (!onlyAttributesWereRead) {
            throw new InvalidOperationException("This operation must be done before retrieving any element");
        }
        onlyAttributesWereRead = false;
 
        GoToDepth(targetDepth);
 
        return (XElement)XNode.ReadFrom(reader); //XElement.Load won't work here if it's not the top level element
    }
 
    private bool GoToDepth(int targetDepth) {
 
        int startDepth = reader.Depth;
 
        if (reader.Depth > targetDepth) { //we need to go up
 
            while (reader.NodeType != XmlNodeType.Element || reader.Depth > targetDepth) {
                if (reader.NodeType == XmlNodeType.Element) {
                    reader.Skip();
                } else {
                    if (!reader.Read()) {
                        return false;
                    }
                    if (reader.Depth > startDepth) {
                        return false;
                    }
                }
            }
 
        } else { //we need to go down (or we're already there)
 
            while (reader.NodeType != XmlNodeType.Element || reader.Depth < targetDepth) {
                if (reader.NodeType == XmlNodeType.Element && reader.IsEmptyElement) {
                    reader.Skip();
                    return false;
                } else {
                    if (!reader.Read()) {
                        return false;
                    }
                    if (reader.Depth < startDepth || reader.NodeType == XmlNodeType.EndElement) {
                        return false;
                    }
                }
            }
        }
 
        return reader.Depth == targetDepth;
    }
 
#endif
 
    void IDisposable.Dispose() {
        Close();
    }
}

Technorati Tags: , ,

Did you know that the Avalon project (the original name of WPF) was started back in 2001, even before .NET Framework 1.0 was released, from the team that originally developed Internet Explorer 4 DHTML support and the XMLHttpRequest API that enables AJAX?

I've learned that and some other interesting trivia from this Channel 9 video:

Michael Wallent: Advent and Evolution of WPF

Technorati Tags: ,

I've been lately looking at some channel 9 videos, and found a lot of great stuff there. Two that I found particularly interesting were these:

Expert to Expert: Contract Oriented Programming and Spec#

Ted Neward: On Java and .NET, Software Academics versus Practioners, General Purpose Programming Languages

Technorati Tags:

Can Erten created a very useful expression tree to CodeDom converter. He wrote about it here: Expression Trees-Lambdas to CodeDom Conversion. I created a visual studio debugger visualizer based on it. You can find it in attachment. Just copy the assembly to My Documents\Visual Studio 2008\Visualizers and you're set. I also attached the full source code.

ExpressionCodeDomVisualizer.dll ExpressionCodeDomVisualizerSourceCode.zip

Technorati Tags: ,

Expression trees are one of the more powerful features of C#. They let you manipulate code in ways that almost remind you of LISP macros (but at runtime instead of compile time). Since I discovered them, I managed to eliminate almost completely the usage of reflection in my code, replacing it with much faster code using techniques similar to what Roger Alsing described in Linq Expressions - Creating objects. Expression trees also made possible something that I find myself using a lot these days: what Jomo Fisher described in Fast Switching with LINQ. It's a great example of the powerful things C# allows you to do with a little creativity. As I used it more and more, I collected a few modifications to the original code, so I though in sharing them here. I named it StaticStringDictionary.

The main difference of this version from the original code is that I don't assume that the key being looked up is in the dictionary. That forces me to call string.Equals at the end to check if the key is the correct one. If string.Equals returns false, a fallback function is used. This also invalidates the optimization in the original code that took advantage of different keys with the same value. I also made StaticStringDictionary<T> implement IDictionary<string, T> so it would be easier to adapt existing code.

Unfortunately I also stumbled over some problems with certain dictionaries, similar to the ones that Raptor-75 wrote about in the comments of the original article. After a few hours of debugging together with Rui Eugénio (and also with the help of Expression Tree Visualizer and StructsViz DebuggerVisualizer), we managed to fix all the problems we found. The comparer was changed to ensure the characters of indices already tested are ignored for the ordering, and in some situations the algorithm has to some backtracking.

Without further due, here's the code:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

public static class StaticStringDictionary {

    public static StaticStringDictionary<Type> Create<Type>(IEnumerable<KeyValuePair<string, Type>> dict, Func<string, Type> fallback) {
        return new StaticStringDictionary<Type>(dict, fallback);
    }
}

public class StaticStringDictionary<Type> : IDictionary<string, Type> {

    private Func<string, Type> fallback;
    private Func<string, Type> switchFunction;

    public StaticStringDictionary(IEnumerable<KeyValuePair<string, Type>> dict, Func<string, Type> fallback) {
        this.fallback = fallback;
        this.switchFunction = CreateSwitch(dict);
    }

    private struct SwitchCase {
        public readonly string Key;
        public readonly Type Value;
        public SwitchCase(string key, Type value) {
            Key = key;
            Value = value;
        }
        public override string ToString() {
            return Key + " " + Value.ToString();
        }
    }

    private Func<string, Type> CreateSwitch(IEnumerable<KeyValuePair<string, Type>> dict) {
        var cases = dict.Select(pair => new SwitchCase(pair.Key, pair.Value)).ToList();
        ParameterExpression keyParameter = Expression.Parameter(typeof(string), "key");
        var expr = Expression.Lambda<Func<string, Type>>(
            SwitchOnLength(keyParameter, cases.OrderBy(switchCase => switchCase.Key.Length).ToArray(), 0, cases.Count - 1),
            new ParameterExpression[] { keyParameter }
        );
        var del = expr.Compile();
        return del;
    }

    private Expression SwitchOnLength(ParameterExpression keyParameter, SwitchCase[] switchCases, int lower, int upper) {                
        if (switchCases[lower].Key.Length == switchCases[upper].Key.Length) {
            return SwitchOnChar(keyParameter, switchCases.Skip(lower).Take(upper - lower + 1).ToArray(), 0, 0, upper - lower);
        }                
        int middle = GetIndexOfFirstDifferentCaseFromUp(switchCases, lower, MidPoint(lower, upper), upper, switchCase => switchCase.Key.Length);
        if (middle == -1) {
            throw new InvalidOperationException();
        }
        return Expression.Condition(
            Expression.LessThan(Expression.Call(keyParameter, stringLength), Expression.Constant(switchCases[middle + 1].Key.Length)),
            SwitchOnLength(keyParameter, switchCases, lower, middle),
            SwitchOnLength(keyParameter, switchCases, middle + 1, upper));
    }

    private Expression SwitchOnChar(ParameterExpression keyParameter, SwitchCase[] switchCases, int index, int lower, int upper) {
        if (index == switchCases[upper].Key.Length) {
            return null;
        }

        if (lower == upper) {
            return Expression.Condition(
                Expression.Call(stringEquals, keyParameter, Expression.Constant(switchCases[lower].Key)),
                Expression.Convert(Expression.Constant(switchCases[lower].Value), typeof(Type)),
                Expression.Invoke(Expression.Constant(fallback), keyParameter));
        }

        switchCases = switchCases.Skip(lower).Take(upper - lower + 1)
            .OrderBy(switchCase => switchCase.Key, StaticStringDictionaryComparer.For(index)).ToArray();

        upper = upper - lower;
        lower = 0;

        int middle = MidPoint(lower, upper);

        if (switchCases[lower].Key[index] == switchCases[middle].Key[index]) {
            var result = SwitchOnChar(keyParameter, switchCases, index + 1, lower, upper);
            if (result != null) {
                return result;
            }
        }

        middle = GetIndexOfFirstDifferentCaseFromUp(switchCases, lower, middle, upper, switchCase => switchCase.Key[index]);
        if (middle == -1) {
            return null;
        }

        var trueBranch = SwitchOnChar(keyParameter, switchCases, index, lower, middle);
        if (trueBranch == null) {
            return null;
        }
        
        var falseBranch = SwitchOnChar(keyParameter, switchCases, index, middle + 1, upper);
        if (falseBranch == null) {
            return null;
        }

        return Expression.Condition(
            Expression.LessThan(Expression.Call(keyParameter, stringIndex, Expression.Constant(index)),
                Expression.Constant(switchCases[middle + 1].Key[index])),
                trueBranch,
                falseBranch);
    }

    private static int MidPoint(int lower, int upper) {
        return ((upper - lower + 1) / 2) + lower;
    }

    private static int GetIndexOfFirstDifferentCaseFromUp<T>(SwitchCase[] cases, int lower, int middle, int upper, Func<SwitchCase, T> selector) {
        T firstValue = selector(cases[middle]);
        for (int i = middle - 1; i >= lower; --i) {
            if (!firstValue.Equals(selector(cases[i]))) {
                return i;
            }
        }
        for (int i = middle + 1; i <= upper; ++i) {
            if (!firstValue.Equals(selector(cases[i]))) {
                return i - 1;
            }
        }
        return -1;
    }

    private static MethodInfo stringLength = typeof(string).GetMethod("get_Length");
    private static MethodInfo stringIndex = typeof(string).GetMethod("get_Chars");
    private static MethodInfo stringEquals = typeof(string).GetMethod("Equals", new[] { typeof(string), typeof(string) });

    public void Add(string key, Type value) {
        throw new InvalidOperationException();
    }

    public bool ContainsKey(string key) {
        throw new InvalidOperationException();
    }

    public ICollection<string> Keys {
        get { throw new InvalidOperationException(); }
    }

    public bool Remove(string key) {
        throw new InvalidOperationException();
    }

    public bool TryGetValue(string key, out Type value) {
        throw new InvalidOperationException();
    }

    public ICollection<Type> Values {
        get { throw new InvalidOperationException(); }
    }

    public Type this[string key] {
        get { return string.IsNullOrEmpty(key) ? fallback(key) : switchFunction(key); }
        set { throw new InvalidOperationException(); }
    }

    public void Add(KeyValuePair<string, Type> item) {
        throw new InvalidOperationException();
    }

    public void Clear() {
        throw new InvalidOperationException();
    }

    public bool Contains(KeyValuePair<string, Type> item) {
        throw new InvalidOperationException();
    }

    public void CopyTo(KeyValuePair<string, Type>[] array, int arrayIndex) {
        throw new InvalidOperationException();
    }

    public int Count {
        get { throw new InvalidOperationException(); }
    }

    public bool IsReadOnly {
        get { return true; }
    }

    public bool Remove(KeyValuePair<string, Type> item) {
        throw new InvalidOperationException();
    }

    public IEnumerator<KeyValuePair<string, Type>> GetEnumerator() {
        throw new InvalidOperationException();
    }

    IEnumerator IEnumerable.GetEnumerator() {
        throw new InvalidOperationException();
    }
}

internal class StaticStringDictionaryComparer : IComparer<string> {

    private readonly int startIndex;
    public StaticStringDictionaryComparer(int startIndex) {
        this.startIndex = startIndex;
    }

    private static Dictionary<int, IComparer<string>> comparers = new Dictionary<int, IComparer<string>>();

    public static IComparer<string> For(int startIndex) {
        IComparer<string> comparer;
        if (!comparers.TryGetValue(startIndex, out comparer)) {
            comparer = new StaticStringDictionaryComparer(startIndex);
            comparers.Add(startIndex, comparer);
        }
        return comparer;
    }

    public int Compare(string x, string y) {

        if (x.Length != y.Length) {
            throw new InvalidOperationException();
        }

        for (int i = startIndex; i < x.Length; i++) {
            if (x[i] > y[i]) {
                return 1;
            } else if (x[i] < y[i]) {
                return -1;
            }
        }

        return 0;
    }
}

And here's the test case for Raptor-75's problem:

public static void Main() {

   var dict = StaticStringDictionary.Create(
       new Dictionary<string, int> {
           {"Ado", 0},
           {"A2o", 1},
           {"A2i", 2},
           {"B2o", 3},
           {"AdoX", 4},
           {"AdPX", 5}
       },
       key => -1);

    Test("Ado", dict);
    Test("A2o", dict);
    Test("A2i", dict);
    Test("B2o", dict);
    Test("AdoX", dict);
    Test("AdPX", dict);
    Test("xpto", dict);
}

private static void Test(string key, IDictionary<string, int> dict) {
    Console.WriteLine(key + ": " + dict[key]);
}

It should produce this output when run:

Ado: 0
A2o: 1
A2i: 2
B2o: 3
AdoX: 4
AdPX: 5
xpto: -1

I also found useful to define this variation when I also need to do reverse lookups:

using System;
using System.Collections.Generic;

public static class DoubleStaticStringDictionary {

    public static DoubleStaticStringDictionary<Type> Create<Type>(IEnumerable<KeyValuePair<string, Type>> dict, Func<string, Type> fallback, Func<Type, string> reverseFallback) {
        return new DoubleStaticStringDictionary<Type>(dict, fallback, reverseFallback);
    }
}

public class DoubleStaticStringDictionary<Type> : StaticStringDictionary<Type>, IDictionary<Type, string> {

    private Func<Type, string> reverseFallback;
    private IDictionary<Type, string> reverseDict;

    public DoubleStaticStringDictionary(IEnumerable<KeyValuePair<string, Type>> dict, Func<string, Type> fallback, Func<Type, string> reverseFallback)
        : base(dict, fallback) {

        this.reverseFallback = reverseFallback;

        reverseDict = new Dictionary<Type, string>();
        foreach (KeyValuePair<string, Type> pair in dict) {
            reverseDict.Add(pair.Value, pair.Key);
        }
    }

    public void Add(Type key, string value) {
        throw new InvalidOperationException();
    }

    public bool ContainsKey(Type key) {
        throw new InvalidOperationException();
    }

    public new ICollection<Type> Keys {
        get { throw new InvalidOperationException(); }
    }

    public bool Remove(Type key) {
        throw new InvalidOperationException();
    }

    public bool TryGetValue(Type key, out string value) {
        throw new InvalidOperationException();
    }

    public new ICollection<string> Values {
        get { throw new InvalidOperationException(); }
    }

    public string this[Type key] {
        get {
            string result;
            if (reverseDict.TryGetValue(key, out result)) {
                return result;
            } else {
                return reverseFallback(key);
            }
        }
        set { throw new InvalidOperationException(); }
    }

    public void Add(KeyValuePair<Type, string> item) {
        throw new InvalidOperationException();
    }

    public bool Contains(KeyValuePair<Type, string> item) {
        throw new InvalidOperationException();
    }

    public void CopyTo(KeyValuePair<Type, string>[] array, int arrayIndex) {
        throw new InvalidOperationException();
    }

    public bool Remove(KeyValuePair<Type, string> item) {
        throw new InvalidOperationException();
    }

    public new IEnumerator<KeyValuePair<Type, string>> GetEnumerator() {
        throw new InvalidOperationException();
    }
}

Technorati Tags: ,
posted @ Thursday, August 28, 2008 9:57 PM | Feedback (2) | Filed Under [ .NET ]

.NET includes a managed MSMQ API (System.Messaging) to send and receive messages, but unfortunately there's a key functionality missing: obtaining the number of messages currently in the queue. Yoel Arnon talks about this problem with more detail here and here. After reading that, we learn that there are three ways to overcome this limitation: using the MSMQ performance counters (with the .NET Performance Counter API or via WMI), using the MSMQ COM wrapper, or using the MSMQ Admin API directly. The first option is not very reliable and the second one is not available on all versions of MSMQ, so the third one is the preferable way. It's also the most complex. Jared Evans has explained how to do it using a C++ Managed wrapper here, but that approach adds some complexity to the deployment of your application. It forces you to redistribute the Microsoft Visual C++ Redistributable Package, and if you want to support the x64 versions of Windows, you have to build the C++ wrapper for both x86 and x64 and have additional logic in the application installer to know which version of your assembly to install (and the same for the Visual C++ Redistributable which also has two versions). Some months ago, I upgraded my home PC to Windows Vista x64, so at the time I decided to develop a C# only solution for this, as there wasn't any publicly available on the net.

The first approach I tried was to look at the mq.h file and translate all the required structures to C#. This proved to be a nightmare, as at the time the P/Invoke Interop Assistant hadn't been created yet and the structures proved to be a little complex. So instead of trying to translate the whole structures correctly, I turned on the debugger and started examining the fields that were really needed, defining dummy spacer fields for the rest. This took a while, but it was worth it. Here's the code:

using System;
using System.Messaging;
using System.Runtime.InteropServices;

static class MessageQueueExtensions {
 
    [DllImport("mqrt.dll")]
    private unsafe static extern int MQMgmtGetInfo(char* computerName, char* objectName, MQMGMTPROPS* mgmtProps);
 
    private const byte VT_NULL = 1;
    private const byte VT_UI4 = 19;
    private const int PROPID_MGMT_QUEUE_MESSAGE_COUNT = 7;
 
    //size must be 16
    [StructLayout(LayoutKind.Sequential)]
    private struct MQPROPVariant {
        public byte vt;       //0
        public byte spacer;   //1
        public short spacer2; //2
        public int spacer3;   //4
        public uint ulVal;    //8
        public int spacer4;   //12
    }
 
    //size must be 16 in x86 and 28 in x64
    [StructLayout(LayoutKind.Sequential)]
    private unsafe struct MQMGMTPROPS {
        public uint cProp;
        public int* aPropID;
        public MQPROPVariant* aPropVar;
        public int* status;
    }   
    
    public static uint GetCount(this MessageQueue queue) {
        return GetCount(queue.Path);
    }
 
    private static unsafe uint GetCount(string path) {
        if (!MessageQueue.Exists(path)) {
            return 0;
        }
 
        MQMGMTPROPS props = new MQMGMTPROPS();
        props.cProp = 1;
 
        int aPropId = PROPID_MGMT_QUEUE_MESSAGE_COUNT;
        props.aPropID = &aPropId;
 
        MQPROPVariant aPropVar = new MQPROPVariant();
        aPropVar.vt = VT_NULL;
        props.aPropVar = &aPropVar;
 
        int status = 0;
        props.status = &status;
 
        IntPtr objectName = Marshal.StringToBSTR("queue=Direct=OS:" + path);
        try {
            int result = MQMgmtGetInfo(null, (char*)objectName, &props);
            if (result != 0 || *props.status != 0 || props.aPropVar->vt != VT_UI4) {
                return 0;
            } else {
                return props.aPropVar->ulVal;
            }
        } finally {
            Marshal.FreeBSTR(objectName);
        }
    }
}

Note that you'll have to go to the project properties and check the "Allow unsafe code" option for this to build.

Here's an example usage:

public static void Main() {
 
    string queueName = @".\Private$\MyQueue";
 
    if (MessageQueue.Exists(queueName)) {
        MessageQueue.Delete(queueName);
    }
 
    MessageQueue queue = MessageQueue.Create(queueName);
    Console.WriteLine("Count should be 0: " + queue.GetCount());
 
    queue.Send("ping", "ping");
    Console.WriteLine("Count should be 1: " + queue.GetCount());
 
    queue.Send("ping2", "ping2");
    Console.WriteLine("Count should be 2: " + queue.GetCount());
 
    queue.Receive();
    Console.WriteLine("Count should be 1: " + queue.GetCount());
 
    queue.Send("ping3", "ping3");
    Console.WriteLine("Count should be 2: " + queue.GetCount());
 
    MessageQueue.Delete(queueName);
    Console.WriteLine("Count should be 0: " + queue.GetCount());
}

After building and running, it should give you the following output:

Count should be 0: 0
Count should be 1: 1
Count should be 2: 2
Count should be 1: 1
Count should be 2: 2
Count should be 0: 0

Last week my colleague Rui Eugénio at OutSystems was doing a merge that involved similar code and commented to me "Couldn't this be done without using unsafe code?". That left me wondering, so I decided to give it a try. It turns out it wasn't that hard. Here's the safe version of the previous code:

using System;
using System.Messaging;
using System.Runtime.InteropServices;
 
public static class MessageQueueExtensions {
 
    [DllImport("mqrt.dll")]
    private static extern int MQMgmtGetInfo([MarshalAs(UnmanagedType.BStr)]string computerName, [MarshalAs(UnmanagedType.BStr)]string objectName, ref MQMGMTPROPS mgmtProps);
 
    private const byte VT_NULL = 1;
    private const byte VT_UI4 = 19;
    private const int PROPID_MGMT_QUEUE_MESSAGE_COUNT = 7;
 
    //size must be 16
    [StructLayout(LayoutKind.Sequential)]
    private struct MQPROPVariant {
        public byte vt;       //0
        public byte spacer;   //1
        public short spacer2; //2
        public int spacer3;   //4
        public uint ulVal;    //8
        public int spacer4;   //12
    }
 
    //size must be 16 in x86 and 28 in x64
    [StructLayout(LayoutKind.Sequential)]
    private struct MQMGMTPROPS {
        public uint cProp;
        public IntPtr aPropID;
        public IntPtr aPropVar;
        public IntPtr status;
    }
 
    public static uint GetCount(this MessageQueue queue) {
        return GetCount(queue.Path);
    }
 
    private static uint GetCount(string path) {
        if (!MessageQueue.Exists(path)) {
            return 0;
        }
 
        MQMGMTPROPS props = new MQMGMTPROPS { cProp = 1 };
        try {
            props.aPropID = Marshal.AllocHGlobal(sizeof(int));
            Marshal.WriteInt32(props.aPropID, PROPID_MGMT_QUEUE_MESSAGE_COUNT);
 
            props.aPropVar = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MQPROPVariant)));
            Marshal.StructureToPtr(new MQPROPVariant { vt = VT_NULL }, props.aPropVar, false);
 
            props.status = Marshal.AllocHGlobal(sizeof(int));
            Marshal.WriteInt32(props.status, 0);
 
            int result = MQMgmtGetInfo(null, "queue=Direct=OS:" + path, ref props);
            if (result != 0 || Marshal.ReadInt32(props.status) != 0) {
                return 0;
            }
 
            MQPROPVariant propVar = (MQPROPVariant)Marshal.PtrToStructure(props.aPropVar, typeof(MQPROPVariant));
            if (propVar.vt != VT_UI4) {
                return 0;
            } else {
                return propVar.ulVal;
            }
        } finally {
            Marshal.FreeHGlobal(props.aPropID);
            Marshal.FreeHGlobal(props.aPropVar);
            Marshal.FreeHGlobal(props.status);
        }
    }
}
 
public class MessageQueueExtensionsTest {
 
    public static void Main() {
 
        string queueName = @".\Private$\MyQueue";
 
        if (MessageQueue.Exists(queueName)) {
            MessageQueue.Delete(queueName);
        }
 
        MessageQueue queue = MessageQueue.Create(queueName);
        Console.WriteLine("Count should be 0: " + queue.GetCount());
 
        queue.Send("ping", "ping");
        Console.WriteLine("Count should be 1: " + queue.GetCount());
 
        queue.Send("ping2", "ping2");
        Console.WriteLine("Count should be 2: " + queue.GetCount());
 
        queue.Receive();
        Console.WriteLine("Count should be 1: " + queue.GetCount());
 
        queue.Send("ping3", "ping3");
        Console.WriteLine("Count should be 2: " + queue.GetCount());
 
        MessageQueue.Delete(queueName);
        Console.WriteLine("Count should be 0: " + queue.GetCount());
    }
}

Technorati Tags: ,

Welcome to my blog. My name is Gustavo Guerra and I'm a software developer at OutSystems R&D. I've been wanting to enter the blogosphere for a long time now, but I always kept postponing it with the excuse that I was too busy with other things. But today I decided that it would be now or never, so here it goes...

I won't be very consistent in the subject of the posts, as I have a few unrelated interests. Some things I plan on talking about are WPF, functional programming, parser development, XML processing, developing with the OutSystems Agile Platform, and photography. I hope you enjoy the content.

Technorati Tags:
posted @ Tuesday, August 26, 2008 6:28 PM | Feedback (0) | Filed Under [ Misc ]