Category Archives: Software

On Redis Desktop Manager and Redis Keys

Update 11th August 2015: As from version 0.8.0 beta 1, Redis Desktop Manager now supports the SCAN command (rather than KEYS) for Redis 2.8 onwards. Although this limits the applicability of this article to older servers, I am more than happy that this shortcoming has been addressed. The original article below remains both for historical purposes and to warn developers of incorrect use of the KEYS command.

There’s a tool called Redis Desktop Manager which can sometimes be useful to inspect the keys in a Redis database. Indeed one of its features is that it presents a treeview showing a structured representation of the keys in the database:

redis-desktop-manager-treeview

But how is this treeview built? That’s easy to find out, by using the Redis MONITOR command to see the incoming commands:

redis-desktop-manager-keys

 

The first two commands are executed when Redis Desktop Manager connects to the Redis server, and the other two are executed when the database is expanded in the treeview, revealing the keys in that database.

You’ll notice that the last command is a KEYS command, which with its wildcard argument (*) is effectively retrieving every key in the database. We can see an example of what this gives us by running the KEYS command ourselves:

redis-desktop-manager-keys-response

Now, in this case I only have a handful of keys in my Redis database, but it’s pretty normal for real Redis databases to have very large numbers of keys. Retrieving all that data places a large burden on the Redis server, which due to its single-threaded nature will not be able to serve other requests while it is stuck retrieving every key in the database.

In fact, the KEYS command documentation particularly warns against its use in production environments:

“Warning: consider KEYS as a command that should only be used in production environments with extreme care. It may ruin performance when it is executed against large databases. This command is intended for debugging and special operations, such as changing your keyspace layout. Don’t use KEYS in your regular application code. If you’re looking for a way to find keys in a subset of your keyspace, consider using SCAN or sets.”

I understand why Redis Desktop Manager uses the KEYS command: it needs to retrieve all the keys in the database in order to determine how the tree structure will be displayed (since each delimited part of the key is rendered as a node). That’s the whole point of having a treeview.

However, what seems to be a useful feature can actually be very dangerous, especially when used on production servers. So do take care when using Redis Desktop Manager in such environments.

My recommendation to developers using Redis is to keep good documentation of your keys, so you won’t need any Redis command to tell you what keys are in the database. That’s not what Redis is for.

But if you do ever need to inspect your Redis keys on the server, at least follow the advice in the documentation and use SCAN instead. While this may still be expensive to retrieve the entire set of keys, it can be done in small batches, thus allowing other requests to be serviced in between iterations.

ImapTalk 2.1 beta released

Today, I released a beta of the next version of ImapTalk, which is 2.1. This new version brings a number of small enhancements and features, but the biggest new feature is that of batch commands. This allows you to send multiple commands at once by entering multiple lines of text – very handy when you prepare a text file full of commands and don’t want to execute them one by one.

Visit the ImapTalk project page to view the full release notes and download the beta.

A Redis Analogy in .NET

In this article, we’re going to create a simple Console Application that feels a little bit like Redis. The intention is simply to illustrate what you can do with Redis, not to create anything serious. For this reason, the code will be greatly simplified: we’ll have only a subset of Redis commands, no multithreading, no client/server code, (almost) no parameter validation, and no architecture whatsoever.

Introduction

Redis is a key-value store. You can think of it as a dictionary. In its simplest sense, it could be represented like this:

var store = new Dictionary<string, string>();

However, values in Redis can be more than just strings. In fact, Redis currently supports five main data types:

  • Strings
  • Lists
  • Hashes
  • Sets
  • Sorted Sets

Since in .NET these data types don’t share a common interface, we will be representing our values as plain objects:

var store = new Dictionary<string, object>();

This will result in some unfortunate type checking code, but this is necessary in this case. Maintaining a separate dictionary per type is not feasible since keys must be globally unique (i.e. you can’t use the same key for a list and a hash).

Note also how the use of a dictionary as an in-memory store means we aren’t persisting anything to disk. This might seem to be an oversimplification, but Redis’ default configuration is actually to store data only in memory. It does support persistence if you need it, but we won’t go there.

Redis supports a simple protocol which feels a bit like issuing commands in a command line if you use something like IMAPTalk. Thus, in our Console Application, we’ll use the following code to simulate Redis’ command processing logic:

            string input = string.Empty;

            while (true)
            {
                input = Console.ReadLine();

                if (!string.IsNullOrEmpty(input))
                {
                    var tokens = input.Split(); // split the input into words or tokens

                    if (tokens.Length >= 1)
                    {
                        string command = tokens.First().ToLowerInvariant();

                        try
                        {
                            switch(command)
                            {
                                    // TODO command handling code goes here
                                default:
                                    Console.WriteLine("Unrecognized command.");
                                    break;
                            }
                        }
                        catch (Exception)
                        {
                            Console.WriteLine("Error!");
                        }
                    }
                }
                else
                    Console.WriteLine("Invalid command.");
            }

The above code just accepts input, checks what the command is, and takes action accordingly. In the next sections, we’ll replace that TODO with code to actually handle some of the commands.

Strings

redis-imaptalk-strings

The simplest data type in Redis is the string. You can easily assign a value to a key using the SET command, and then retrieve that value using the GET command, as shown above. The SET command is just a matter of assigning the value in the dictionary:

                                case "set":
                                    {
                                        string key = tokens[1];
                                        string value = tokens[2];
                                        store[key] = value;
                                        Console.WriteLine("Done.");
                                    }
                                    break;

The GET command similarly involves basic retrieval from the dictionary, but we also need to cater for non-existent keys, and values that are not strings:

                                case "get":
                                    {
                                        string key = tokens[1];
                                        if (store.ContainsKey(key))
                                        {
                                            var value = store[key] as string;
                                            if (value != null)
                                                Console.WriteLine(value);
                                            else
                                                Console.WriteLine("Invalid data type!");
                                        }
                                        else
                                            Console.WriteLine("Key not found!");
                                    }
                                    break;

We can also support key removal using the DEL command. Again, this is a simple operation against the dictionary:

                                case "del":
                                    {
                                        string key = tokens[1];
                                        store.Remove(key);
                                        Console.WriteLine("Done.");
                                    }
                                    break;

We can now test our three basic string commands:

redis-analogy-strings

Lists

redis-imaptalk-lists

Redis also supports Lists. You can use the LPUSH and RPUSH commands to add an item to the beginning or end of a list respectively – allowing you to easily use the list as a stack or a queue as well. These operations also automatically create the list if it doesn’t already exist.

Here’s how we can implement LPUSH:

                                case "lpush":
                                    {
                                        string key = tokens[1];
                                        string value = tokens[2];
                                        List<string> list = null;

                                        // create/get list

                                        if (!store.ContainsKey(key))
                                        {
                                            list = new List<string>();
                                            store[key] = list;
                                        }
                                        else
                                            list = store[key] as List<string>;

                                        // insert new value in list

                                        if (list != null)
                                        {
                                            list.Insert(0, value);
                                            Console.WriteLine("Done");
                                        }
                                        else
                                            Console.WriteLine("Invalid data type!");
                                    }
                                    break;

The first thing we do here is retrieve the list we’re going to work with. If it doesn’t exist, we create it. We can then proceed to insert the new item into the list – in this case using List<T>.Insert() to put the item at the beginning of the list.

Note that in Redis, LPUSH actually supports insertion of multiple items with the same command. I’m not doing that here to keep things as simple as possible.

RPUSH is pretty much the same thing, except that you call List<T>.Add() instead of .Insert(), so that the new item ends up at the end of the list.

To remove items from the list, we’ll implement LPOP and RPOP, which remove items from the beginning and end of the list respectively. Here’s LPOP:

                                case "lpop":
                                    {
                                        string key = tokens[1];
                                        
                                        if (store.ContainsKey(key))
                                        {
                                            var list = store[key] as List<string>;
                                            if (list != null)
                                            {
                                                if (list.Count > 0)
                                                {
                                                    Console.WriteLine(list.First());
                                                    list.RemoveAt(0);
                                                }
                                                else
                                                    Console.WriteLine("Empty!");
                                            }
                                            else
                                                Console.WriteLine("Invalid data type!");
                                        }
                                        else
                                            Console.WriteLine("Key not found!");
                                    }
                                    break;

Aside from all the validity checks, all we’re doing here is returning the first item in the list, and then removing it. If there are no items in the list, we simply return “Empty!”.

For RPOP, the only difference is that we retrieve and remove the last item instead:

                                                    Console.WriteLine(list.Last());
                                                    list.RemoveAt(list.Count - 1);

Finally, we need something that can retrieve items in the list (without removing them). For this we have LRANGE, which retrieves a specified range of items (e.g. item #2 till item #4) from the beginning of the list. Indices may be out of range without causing errors; the indices that actually exist will be returned.

Here is the implementation for LRANGE:

                                case "lrange":
                                    {
                                        string key = tokens[1];
                                        int start = Convert.ToInt32(tokens[2]);
                                        int stop = Convert.ToInt32(tokens[3]);

                                        if (start > stop)
                                            Console.WriteLine("Empty!");
                                        else if (store.ContainsKey(key))
                                        {
                                            var list = store[key] as List<string>;
                                            if (list != null)
                                            {
                                                if (start < 0)
                                                    start = 0;

                                                if (stop > list.Count - 1)
                                                    stop = list.Count - 1;

                                                var items = list.GetRange(start, stop - start + 1);
                                                if (items.Any())
                                                {
                                                    foreach(var item in items)
                                                        Console.WriteLine(item);
                                                }
                                                else
                                                    Console.WriteLine("Empty!");
                                            }
                                            else
                                                Console.WriteLine("Invalid data type!");
                                        }
                                        else
                                            Console.WriteLine("Key not found!");
                                    }
                                    break;

We can now test our list functionality:

redis-analogy-lists

Hashes

redis-imaptalk-hashes

Hashes are like dictionaries in themselves. Rather than mapping a value directly to a key, hashes map values onto a field of a key. This allows you to represent objects with a number of attributes (e.g. a customer having separate values for Name, Age, etc).

With HSET, we can assign a value to a key-field, as shown in the screenshot above. Like with Lists, this operation creates the Hash if it doesn’t already exist. Here’s the HSET implementation:

                                case "hset":
                                    {
                                        string key = tokens[1];
                                        string field = tokens[2];
                                        string value = tokens[3];
                                        Dictionary<string, string> hash = null;

                                        // create/get hash

                                        if (!store.ContainsKey(key))
                                        {
                                            hash = new Dictionary<string, string>();
                                            store[key] = hash;
                                        }
                                        else
                                            hash = store[key] as Dictionary<string, string>;

                                        // set field in hash

                                        if (hash != null)
                                        {
                                            hash[field] = value;
                                            Console.WriteLine("Done");
                                        }
                                        else
                                            Console.WriteLine("Invalid data type!");
                                    }
                                    break;

The code is very similar to that of LPUSH, with the difference that the key now maps to a dictionary (the hash), and the value is assigned to a field on that hash. Think of it as: key -> field -> value.

After using HSET to set a value, we can retrieve it with HGET:

                                case "hget":
                                    {
                                        string key = tokens[1];
                                        string field = tokens[2];

                                        if (store.ContainsKey(key))
                                        {
                                            var hash = store[key] as Dictionary<string, string>;

                                            if (hash != null)
                                            {
                                                if (hash.ContainsKey(field))
                                                    Console.WriteLine(hash[field]);
                                                else
                                                    Console.WriteLine("Field not found!");
                                            }
                                            else
                                                Console.WriteLine("Invalid data type!");
                                        }
                                        else
                                            Console.WriteLine("Key not found!");
                                    }
                                    break;

HKEYS can be used to retrieve all fields of a hash, given the key:

                                case "hkeys":
                                    {
                                        string key = tokens[1];

                                        if (store.ContainsKey(key))
                                        {
                                            var hash = store[key] as Dictionary<string, string>;

                                            if (hash != null)
                                            {
                                                foreach (var field in hash.Keys)
                                                    Console.WriteLine(field);
                                            }
                                            else
                                                Console.WriteLine("Invalid data type!");
                                        }
                                        else
                                            Console.WriteLine("Key not found!");
                                    }
                                    break;

HGETALL, on the other hand, gets all fields of a hash and their corresponding values:

                                case "hgetall":
                                    {
                                        string key = tokens[1];

                                        if (store.ContainsKey(key))
                                        {
                                            var hash = store[key] as Dictionary<string, string>;

                                            if (hash != null)
                                            {
                                                foreach (var kvp in hash)
                                                {
                                                    Console.WriteLine(kvp.Key);
                                                    Console.WriteLine(kvp.Value);
                                                }
                                            }
                                            else
                                                Console.WriteLine("Invalid data type!");
                                        }
                                        else
                                            Console.WriteLine("Key not found!");
                                    }
                                    break;

Let’s test out our hash functionality:

redis-analogy-hashes

Sets

redis-imaptalk-sets

Sets in Redis are the mathematical kind: all items in a set are distinct, and multiple sets may be combined via standard set operations (union, intersection and difference).

Use SADD to add one or more items to a set. If the set does not exist, it will be created automatically. Here’s the SADD implementation:

                                case "sadd":
                                    {
                                        string key = tokens[1];
                                        var members = tokens.Skip(2); // sadd key member [member ...]
                                        HashSet<string> set = null;

                                        // create/get set

                                        if (!store.ContainsKey(key))
                                        {
                                            set = new HashSet<string>();
                                            store[key] = set;
                                        }
                                        else
                                            set = store[key] as HashSet<string>;

                                        // add member to set

                                        if (set != null)
                                        {
                                            foreach (var member in members)
                                                set.Add(member);

                                            Console.WriteLine("Done");
                                        }
                                        else
                                            Console.WriteLine("Invalid data type!");
                                    }
                                    break;

SMEMBERS gives you all the members of the set:

                                case "smembers":
                                    {
                                        string key = tokens[1];

                                        if (store.ContainsKey(key))
                                        {
                                            var set = store[key] as HashSet<string>;

                                            if (set != null)
                                            {
                                                foreach (var member in set)
                                                    Console.WriteLine(member);
                                            }
                                            else
                                                Console.WriteLine("Invalid data type!");
                                        }
                                        else
                                            Console.WriteLine("Key not found!");
                                    }
                                    break;

Standard set operations (union, intersection and difference) require two sets. If either doesn’t exist, these operations will return an empty set rather than giving any kind of error. In Redis, these operations may work on multiple sets at once; but in these examples we’re going to limit the scenario to two sets at a time for the sake of simplicity.

Set union (SUNION) returns all distinct items in the specified sets:

                                case "sunion":
                                    {
                                        List<HashSet<string>> sets = new List<HashSet<string>>();

                                        var key1 = tokens[1];
                                        var key2 = tokens[2];

                                        // let's assume the keys exist and are the correct type

                                        var set1 = store[key1] as HashSet<string>;
                                        var set2 = store[key2] as HashSet<string>;

                                        // get union

                                        var union = set1.Union(set2);
                                        foreach(var member in union)
                                            Console.WriteLine(member);
                                    }
                                    break;

Set intersection (SINTER) returns those items which are common to both sets:

                                case "sinter":
                                    {
                                        List<HashSet<string>> sets = new List<HashSet<string>>();

                                        var key1 = tokens[1];
                                        var key2 = tokens[2];

                                        // let's assume the keys exist and are the correct type

                                        var set1 = store[key1] as HashSet<string>;
                                        var set2 = store[key2] as HashSet<string>;

                                        // get intersection

                                        var union = set1.Intersect(set2);
                                        foreach (var member in union)
                                            Console.WriteLine(member);
                                    }
                                    break;

Finally, set difference (SDIFF) returns those items which are in the first set but which are not in the second:

                                case "sdiff":
                                    {
                                        List<HashSet<string>> sets = new List<HashSet<string>>();

                                        var key1 = tokens[1];
                                        var key2 = tokens[2];

                                        // let's assume the keys exist and are the correct type

                                        var set1 = store[key1] as HashSet<string>;
                                        var set2 = store[key2] as HashSet<string>;

                                        // get intersection

                                        var union = set1.Except(set2);
                                        foreach (var member in union)
                                            Console.WriteLine(member);
                                    }
                                    break;

Let’s test that out:

redis-analogy-sets

Sorted Sets

redis-imaptalk-sortedsets

Sorted Sets in Redis are similar to sets, but their members are sorted by a score. This may be used for any kind of ordering from the latest 5 comments to the top 5 posts with most likes.

We can represent a Sorted Set similarly to a hash, using the scheme key -> score -> value. However, there are two main differences from a hash:

  • For each score, there may be multiple values; these are sorted lexicographically.
  • The scores are ordered (sorted).

The implementation of a Sorted Set in .NET is not as trivial as the other data types, and requires a combination of collections. While a simple representation could be made by combining a SortedDictionary (score -> values) with a SortedSet (distinct and sorted values), this would not allow all Sorted Set commands to be supported (e.g. ZRANK can find the index of a given value, requiring a reverse lookup).

Sorted Sets are thus beyond the scope of this article, which is intended to provide a simple mapping between Redis data types and .NET collections.

Summary

This article explained how the Redis data types work, and showed how some of their operations may be implemented using standard .NET collections. A typical mapping could be:

  • Strings – strings
  • Lists – Lists
  • Hashes – Dictionaries or ConcurrentDictionaries
  • Sets – HashSets
  • Sorted Sets – a custom data structure

Source Code

The source code for this article is available on BitBucket.

IMAPTalk 2 (final) Released

The final version of IMAPTalk 2 has finally been released. This version is mostly the same as the beta 3 release from last week. IMAPTalk 2 had been in beta since October, allowing ample time for testing.

The IMAPTalk page has also been updated to reflect the new version. Some older downloads, instructions and screenshots from the early versions have been removed. Go there to download the latest version!

Remember that since beta 3, IMAPTalk can be used for manual interaction with Redis servers. I am planning some new tools (for upcoming releases) that should be useful when using IMAPTalk for Redis.

IMAPTalk 2 beta 3 released

I have just released a new beta release of IMAPTalk 2. While the changes involve fairly minor enhancements, I am more excited to have found another protocol that IMAPTalk is compatible with. It’s RESP, the protocol used by Redis. You can use IMAPTalk to communicate with a Redis server instead of the Redis CLI on Windows, giving you several advantages including colorization and standard rich text features.

Update the updater to update

update-windows-update

I formatted one of my laptops and reinstalled Windows the other day. Whilst reinstalling software and bringing it up to date, Windows update gave me this ridiculous message:

“To check for updates, you must first install an update for Windows Update.”

I guess software has become so complex that even updates require various levels of abstraction. 🙂

VS2015 Preview: Layout Management

In earlier versions of Visual Studio, if you happened to mess up your window layout in Visual Studio, you could reset it back to the default layout by using the appropriate item in the Window menu:

vs2015-windowlayouts-duringreset

This rearranges the docked windows to whatever you originally had when Visual Studio was installed:

vs2015-windowlayouts-afterreset

That’s nice and all. But you may have noticed some new items above “Reset Window Layout” in the Window menu which are pretty handy when it comes to managing your window layouts.

For instance, when developing a new WPF application, the toolbox can sometimes come in handy for those without much experience with XAML. So, after introducing the Toolbox window, you can save the current layout:

vs2015-windowlayouts-duringsave

To save a layout, you need to give it a name:

vs2015-windowlayouts-promptsave

…and if that name happens to already exist, you’re asked whether you want to replace it (this is how you update saved layouts):

vs2015-windowlayouts-promptsaveexisting

You can then load (apply) these layouts by selecting them from the list in the Window menu, or by using the appropriate shortcut key (available for the first nine layouts in order). For instance, I saved this alternate layout suitable for unit tests, and I can apply it like this:

vs2015-windowlayouts-duringapply

…and then, like magic, the selected layout is applied:

vs2015-windowlayouts-afterapply

There’s also a menu item for management of these layouts:

vs2015-windowlayouts-managemenuitem

This opens a dialog which allows you to rename, delete, or reorder layouts.

vs2015-windowlayouts-managedialog

Reordering layouts has the effect of reassigning their keyboard shortcuts, since they are assigned in order to the first nine (from Ctrl+Alt+1 to Ctrl+Alt+9).

So, there you have it! Window layout management is yet another new feature in Visual Studio 2015 to improve your productivity.

VS2015 Preview: Live Static Code Analysis

In Visual Studio 2015, the new C# and VB .NET compilers are based on the .NET Compiler Platform known as Roslyn. This allows third-party tools to use the compiler itself to query the structure of code, rather than having to redo the compiler’s job. It also provides the ability to fix the code by modifying its structure.

You can use the NuGet Package Manager in VS2015 to install analyzers. At the moment there are very few available, and they’re still prerelease software. For this demonstration we’ll use the CodeCracker C# analyzer:

vs2015-analyzers-nuget

Once you install the package, you’ll see that the analyzer has been added under a special Analyzers node under the References in Solution Explorer. If you expand the analyzer, you can see all the rules that it enforces:

vs2015-analyzers-solution-explorer

As you can see, the code analysis rules may be enforced at various levels. Those marked as errors will result in actual compiler errors, and obviously cause the build to fail.

The rules that are broken by the code will then show up in the error list, which in VS2015 has been enhanced so that you can see more info about the error/rule and also click on the error code link to go to its online documentation (at the time of writing this article, the CodeCracker’s error code links are broken):

vs2015-analyzers-error-list

The best thing about live static code analysis is that they’re live: the rules will be checked as you’re writing your code, and you don’t even need to build for them to be evaulated.

Warnings may be sorted out by moving the caret within the relevant code and pressing Ctrl+. (Control Dot), after which the Quick Actions become available. See, code analyzers don’t need to limit themselves to complaining. If a fix is available, you’ll have the option to apply it, and you can preview the changes as with any other refactoring:

vs2015-analyzers-fix

If you know better than the code analyzer, you can opt to suppress the warning instead:

vs2015-analyzers-suppress

Code analysis allows rules to be enforced on your team’s code. Roslyn facilitates this by making it easy to plug in different code analyzers (and hopefully in future there will be a wider range to choose from) and by running code analysis live, without the need to build the project.

Note: this demonstration used the CodeCracker C# analyzer in order to show the different levels of rules (e.g. warnings, errors, etc), since the StyleCop analyzer’s rules are all warnings. The CodeCracker C# analyzer is in quite a mess at the time of writing, with missing fixes, broken rule documentation links, and countless grammatical errors. But it’s prerelease, so we’ll pretend that’s a good excuse and forgive it for its sins.

VS2015 Preview: NuGet 3 Preview

Well, what do you know. It seems like NuGet’s been to the hairdresser recently.  In fact, in Visual Studio 2015 you’ll find the preview of NuGet 3.0, which looks like this:

vs2015-nuget3-layout

It’s obviously changed quite a bit from the version we use today, which for the sake of comparison is this:

vs2015-old-nuget

The most obvious thing to notice is that NuGet is now a first-class citizen with its own page in Visual Studio, rather than being a little modal window. There are other layout improvements you’ll notice, such as the fact that they’ve done away with the separation between installed, installable and updatable packages. In fact, the new NuGet experience is a unified one in which you can filter the packages you want to see: All, Installed, or Update Available.

Usability is not all that is being improved in NuGet 3.0. In fact, there are a bunch of new features that weren’t in NuGet before. One that I am very happy to see is the ability to select the package version to install, rather than having to resort to the Package Manager Console to install specific versions.

Another really cool feature is that of package consolidation. Have you ever had something like 4 different versions of JSON.NET across your solution, and then had to painfully manually converge them into a single version? NuGet 3 allows you to consolidate different versions of the same assembly pretty easily. Just select the version you want, and select the “Consolidate” action, and click the “Consolidate” button:

vs2015-nuget3-consolidate

There are also a few other features, such as a Preview button showing what actions will be taken when you execute a change, and a couple of advanced options. For full details on what’s new, check out the official announcement.

Oh, and just in case you’re curious… that funny thingy next to the Search box that looks like a ship’s wheel is actually supposed to be a gear icon, because it takes you into the NuGet settings.

Roslyn moves to GitHub

For the past several weeks I’ve been writing about the new features in C# 6 and Visual Studio 2015, which are facilitated by the .NET Compiler Platform, known as Roslyn.

Roslyn’s Codeplex homepage has hosted the project’s source code and information for a while.

However, over the past few days, the following announcement was posted on Roslyn’s Codeplex homepage:

This coming week (Wednesday is the target, Thursday at the latest) we will be moving the Roslyn Project to live under GitHub, joining the rest of the .NET team over there.

Details:

  • This will be a simple switch – turn off CodePlex, turn on GitHub. You’ll be able to see our checkins on GitHub that same day, for example.
  • Any of your pull requests to our project in GitHub will pile up for a couple of weeks, because we are going to take the opportunity to also streamline our (currently very complex) pull request process – yeah! We’ll reopen in a couple weeks with a much easier process. That, combined with us switching to use Git internally as well at the same time (!), means many fewer moving parts and gets us much closer to the same environment you’ll be using on Roslyn code. It will be so worth it.
    • At this point, I’d advise holding off on any requests sent to CodePlex, and save them for GitHub instead.
  • We’ll be using GitHub Issues for both discussions and bugs after the switch.
  • We will try to move over outstanding bugs from CodePlex, but this is the trickier part of the plan. Stay tuned.
  • We will also do our best to preserve check-in history.
  • We will be under the .NET Foundation over there, as the “Compilers” project.

We will update this page with the forwarding information when the switch is complete mid-week. I’ll also be blogging about some of the additional OSS work we’re about to embark on in a week or two.

–Matt Gertz–*
Group Software Engineering Manager, “Roslyn”

It appears that the move is now complete, and you can find all the latest on Roslyn at its new home on GitHub.