And hopefully many more will follow suit. SharePoint 2010 also does not support IE6. Hopefully this will hasten it’s demise.

Dear Google Apps admin,​

In order to continue to improve our products and deliver more sophisticated features and performance, we are harnessing some of the latest improvements in web browser technology.  This includes faster JavaScript processing and new standards like HTML5.  As a result, over the course of 2010, we will be phasing out support for Microsoft Internet Explorer 6.0 as well as other older browsers that are not supported by their own manufacturers.

We plan to begin phasing out support of these older browsers on the Google Docs suite and the Google Sites editor on March 1, 2010.  After that point, certain functionality within these applications may have higher latency and may not work correctly in these older browsers. Later in 2010, we will start to phase out support for these browsers for Google Mail and Google Calendar.

Google Apps will continue to support Internet Explorer 7.0 and above, Firefox 3.0 and above, Google Chrome 4.0 and above, and Safari 3.0 and above.

Starting this week, users on these older browsers will see a message in Google Docs and the Google Sites editor explaining this change and asking them to upgrade their browser.  We will also alert you again closer to March 1 to remind you of this change.

In 2009, the Google Apps team delivered more than 100 improvements to enhance your product experience. We are aiming to beat that in 2010 and continue to deliver the best and most innovative collaboration products for businesses.

Thank you for your continued support!

Sincerely,

The Google Apps team

Email preferences: You have received this mandatory email service announcement to update you about important changes to your Google Apps product or account.

Google Inc.
1600 Amphitheatre Parkway
Mountain View, CA 94043

These ones are pretty simple in implementation but go a long way to cleaning up your code. In some cases, the slight performance overhead may not be acceptable but I haven’t run into any real-world problems with them. Check out the usage below.

int x = 3;

// is x either 1, 3, or 5?
if (x.In(1, 3, 5)) {
    // yes!
}

// is x between 1 and 5?
if (x.Between(1, 5)) {
    // yes!
}

The In method works with any kind of object and uses the default EqualityComparer<T> implementation for the type. But there are overloads that specify common types such as int, byte, long, etc. These overloads will perform much better than the generic overload. There is also an overload for String that takes a comparer so that you can specify case-insensitive matching as needed.

The Between method works on any type implementing IComparable. It also has overloads for commonly used types to get better performance than using the default IComparable implementation.

The code for both extension methods is shown below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Einstein
{

    /// <summary>
    /// Extension methods for <see cref="T:IComparable"/> types.
    /// </summary>
    public static class ComparableExtensions
    {

        #region Methods

        #region In

        /// <summary>
        /// Determines whether this value exists in the <paramref name="set"/> passed.
        /// </summary>
        /// <typeparam name="T">The type of the value to compare and the values in the set.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="comparer">The comparer to use.</param>
        /// <param name="set">The set of values to determine if <paramref name="value"/> is in.</param>
        /// <returns>True if <paramref name="value"/> exists in the <paramref name="set"/> otherwise, false.</returns>
        public static bool In<T>( this T value, IEqualityComparer<T> comparer, params T[] set )
        {
            
            // Use default comparer
            if ( comparer == null ) {
                comparer = EqualityComparer<T>.Default;
            }   // if

            // Constant false
            if ( set.Length == 0 ) {
                return false;
            }   // if

            for ( int i = 0; i < set.Length; i++ ) {
                if ( comparer.Equals( value, set[i] ) ) {
                    return true;
                }   // if
            }   // for

            return false;

        }

        /// <summary>
        /// Determines whether this value exists in the <paramref name="set"/> passed.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="set">The set of values to determine if <paramref name="value"/> is in.</param>
        /// <returns>True if <paramref name="value"/> exists in the <paramref name="set"/> otherwise, false.</returns>
        public static bool In( this byte value, params byte[] set )
        {
            for ( int i = 0; i < set.Length; i++ ) {
                if ( value == set[i] ) {
                    return true;
                }   // if
            }   // for
            return false;
        }

        /// <summary>
        /// Determines whether this value exists in the <paramref name="set"/> passed.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="set">The set of values to determine if <paramref name="value"/> is in.</param>
        /// <returns>True if <paramref name="value"/> exists in the <paramref name="set"/> otherwise, false.</returns>
        public static bool In( this short value, params short[] set )
        {
            for ( int i = 0; i < set.Length; i++ ) {
                if ( value == set[i] ) {
                    return true;
                }   // if
            }   // for
            return false;
        }

        /// <summary>
        /// Determines whether this value exists in the <paramref name="set"/> passed.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="set">The set of values to determine if <paramref name="value"/> is in.</param>
        /// <returns>True if <paramref name="value"/> exists in the <paramref name="set"/> otherwise, false.</returns>
        public static bool In( this int value, params int[] set )
        {
            for ( int i = 0; i < set.Length; i++ ) {
                if ( value == set[i] ) {
                    return true;
                }   // if
            }   // for
            return false;
        }

        /// <summary>
        /// Determines whether this value exists in the <paramref name="set"/> passed.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="set">The set of values to determine if <paramref name="value"/> is in.</param>
        /// <returns>True if <paramref name="value"/> exists in the <paramref name="set"/> otherwise, false.</returns>
        public static bool In( this long value, params long[] set )
        {
            for ( int i = 0; i < set.Length; i++ ) {
                if ( value == set[i] ) {
                    return true;
                }   // if
            }   // for
            return false;
        }

        /// <summary>
        /// Determines whether this value exists in the <paramref name="set"/> passed.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="set">The set of values to determine if <paramref name="value"/> is in.</param>
        /// <returns>True if <paramref name="value"/> exists in the <paramref name="set"/> otherwise, false.</returns>
        public static bool In( this string value, params string[] set )
        {
            return In<string>( value, StringComparer.OrdinalIgnoreCase, set );
        }

        /// <summary>
        /// Determines whether this value exists in the <paramref name="set"/> passed.
        /// </summary>
        /// <typeparam name="T">The type of the value to compare and the values in the set.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="set">The set of values to determine if <paramref name="value"/> is in.</param>
        /// <returns>True if <paramref name="value"/> exists in the <paramref name="set"/> otherwise, false.</returns>
        public static bool In<T>( this T value, params T[] set )
        {
            return In<T>( value, EqualityComparer<T>.Default, set );
        }

        #endregion

        #region Between

        /// <summary>
        /// Determines if the <paramref name="value"/> is between <paramref name="minValueInclusive"/> and
        /// <paramref name="maxValueInclusive"/>.
        /// </summary>
        /// <typeparam name="T">The type of values to compare.</typeparam>
        /// <param name="value">The value to check.</param>
        /// <param name="minValueInclusive">The min value inclusive.</param>
        /// <param name="maxValueInclusive">The max value inclusive.</param>
        /// <returns>True if <paramref name="value"/> is between <paramref name="minValueInclusive"/> and 
        /// <paramref name="maxValueInclusive"/>, otherwise false.</returns>
        public static bool Between<T>( this T value, T minValueInclusive, T maxValueInclusive ) where T : IComparable
        {

            ParameterValidation.ThrowIfNull( value, "value" );

            int o1 = value.CompareTo( minValueInclusive );
            int o2 = value.CompareTo( maxValueInclusive );

            return ( o1 >= 0 && o2 <= 0 ) || ( o1 <= 0 && o2 >= 0 );

        }

        /// <summary>
        /// Determines if the <paramref name="value"/> is between <paramref name="minValueInclusive"/> and
        /// <paramref name="maxValueInclusive"/>.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <param name="minValueInclusive">The min value inclusive.</param>
        /// <param name="maxValueInclusive">The max value inclusive.</param>
        /// <returns>True if <paramref name="value"/> is between <paramref name="minValueInclusive"/> and 
        /// <paramref name="maxValueInclusive"/>, otherwise false.</returns>
        public static bool Between( this byte value, byte minValueInclusive, byte maxValueInclusive )
        {
            if ( minValueInclusive <= maxValueInclusive ) {
                return ( value >= minValueInclusive ) && ( value <= maxValueInclusive );
            }   // if
            else {
                return ( value >= maxValueInclusive ) && ( value <= minValueInclusive );
            }   // else
        }

        /// <summary>
        /// Determines if the <paramref name="value"/> is between <paramref name="minValueInclusive"/> and
        /// <paramref name="maxValueInclusive"/>.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <param name="minValueInclusive">The min value inclusive.</param>
        /// <param name="maxValueInclusive">The max value inclusive.</param>
        /// <returns>True if <paramref name="value"/> is between <paramref name="minValueInclusive"/> and 
        /// <paramref name="maxValueInclusive"/>, otherwise false.</returns>
        public static bool Between( this short value, short minValueInclusive, short maxValueInclusive )
        {
            if ( minValueInclusive <= maxValueInclusive ) {
                return ( value >= minValueInclusive ) && ( value <= maxValueInclusive );
            }   // if
            else {
                return ( value >= maxValueInclusive ) && ( value <= minValueInclusive );
            }   // else
        }

        /// <summary>
        /// Determines if the <paramref name="value"/> is between <paramref name="minValueInclusive"/> and
        /// <paramref name="maxValueInclusive"/>.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <param name="minValueInclusive">The min value inclusive.</param>
        /// <param name="maxValueInclusive">The max value inclusive.</param>
        /// <returns>True if <paramref name="value"/> is between <paramref name="minValueInclusive"/> and 
        /// <paramref name="maxValueInclusive"/>, otherwise false.</returns>
        public static bool Between( this int value, int minValueInclusive, int maxValueInclusive )
        {
            if ( minValueInclusive <= maxValueInclusive ) {
                return ( value >= minValueInclusive ) && ( value <= maxValueInclusive );
            }   // if
            else {
                return ( value >= maxValueInclusive ) && ( value <= minValueInclusive );
            }   // else
        }

        /// <summary>
        /// Determines if the <paramref name="value"/> is between <paramref name="minValueInclusive"/> and
        /// <paramref name="maxValueInclusive"/>.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <param name="minValueInclusive">The min value inclusive.</param>
        /// <param name="maxValueInclusive">The max value inclusive.</param>
        /// <returns>True if <paramref name="value"/> is between <paramref name="minValueInclusive"/> and 
        /// <paramref name="maxValueInclusive"/>, otherwise false.</returns>
        public static bool Between( this long value, long minValueInclusive, long maxValueInclusive )
        {
            if ( minValueInclusive <= maxValueInclusive ) {
                return ( value >= minValueInclusive ) && ( value <= maxValueInclusive );
            }   // if
            else {
                return ( value >= maxValueInclusive ) && ( value <= minValueInclusive );
            }   // else
        }

        /// <summary>
        /// Determines if the <paramref name="value"/> is between <paramref name="minValueInclusive"/> and
        /// <paramref name="maxValueInclusive"/>.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <param name="minValueInclusive">The min value inclusive.</param>
        /// <param name="maxValueInclusive">The max value inclusive.</param>
        /// <returns>True if <paramref name="value"/> is between <paramref name="minValueInclusive"/> and 
        /// <paramref name="maxValueInclusive"/>, otherwise false.</returns>
        public static bool Between( this decimal value, decimal minValueInclusive, decimal maxValueInclusive )
        {
            if ( minValueInclusive <= maxValueInclusive ) {
                return ( value >= minValueInclusive ) && ( value <= maxValueInclusive );
            }   // if
            else {
                return ( value >= maxValueInclusive ) && ( value <= minValueInclusive );
            }   // else
        }

        /// <summary>
        /// Determines if the <paramref name="value"/> is between <paramref name="minValueInclusive"/> and
        /// <paramref name="maxValueInclusive"/>.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <param name="minValueInclusive">The min value inclusive.</param>
        /// <param name="maxValueInclusive">The max value inclusive.</param>
        /// <returns>True if <paramref name="value"/> is between <paramref name="minValueInclusive"/> and 
        /// <paramref name="maxValueInclusive"/>, otherwise false.</returns>
        public static bool Between( this float value, float minValueInclusive, float maxValueInclusive )
        {
            if ( minValueInclusive <= maxValueInclusive ) {
                return ( value >= minValueInclusive ) && ( value <= maxValueInclusive );
            }   // if
            else {
                return ( value >= maxValueInclusive ) && ( value <= minValueInclusive );
            }   // else
        }

        /// <summary>
        /// Determines if the <paramref name="value"/> is between <paramref name="minValueInclusive"/> and
        /// <paramref name="maxValueInclusive"/>.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <param name="minValueInclusive">The min value inclusive.</param>
        /// <param name="maxValueInclusive">The max value inclusive.</param>
        /// <returns>True if <paramref name="value"/> is between <paramref name="minValueInclusive"/> and 
        /// <paramref name="maxValueInclusive"/>, otherwise false.</returns>
        public static bool Between( this double value, double minValueInclusive, double maxValueInclusive )
        {
            if ( minValueInclusive <= maxValueInclusive ) {
                return ( value >= minValueInclusive ) && ( value <= maxValueInclusive );
            }   // if
            else {
                return ( value >= maxValueInclusive ) && ( value <= minValueInclusive );
            }   // else
        }

        /// <summary>
        /// Determines if the <paramref name="value"/> is between <paramref name="minValueInclusive"/> and
        /// <paramref name="maxValueInclusive"/>.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <param name="minValueInclusive">The min value inclusive.</param>
        /// <param name="maxValueInclusive">The max value inclusive.</param>
        /// <returns>True if <paramref name="value"/> is between <paramref name="minValueInclusive"/> and 
        /// <paramref name="maxValueInclusive"/>, otherwise false.</returns>
        public static bool Between( this DateTime value, DateTime minValueInclusive, DateTime maxValueInclusive )
        {
            if ( minValueInclusive <= maxValueInclusive ) {
                return ( value >= minValueInclusive ) && ( value <= maxValueInclusive );
            }   // if
            else {
                return ( value >= maxValueInclusive ) && ( value <= minValueInclusive );
            }   // else
        }

        /// <summary>
        /// Determines if the <paramref name="value"/> is between <paramref name="minValueInclusive"/> and
        /// <paramref name="maxValueInclusive"/>.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <param name="minValueInclusive">The min value inclusive.</param>
        /// <param name="maxValueInclusive">The max value inclusive.</param>
        /// <returns>True if <paramref name="value"/> is between <paramref name="minValueInclusive"/> and 
        /// <paramref name="maxValueInclusive"/>, otherwise false.</returns>
        public static bool Between( this TimeSpan value, TimeSpan minValueInclusive, TimeSpan maxValueInclusive )
        {
            if ( minValueInclusive <= maxValueInclusive ) {
                return ( value >= minValueInclusive ) && ( value <= maxValueInclusive );
            }   // if
            else {
                return ( value >= maxValueInclusive ) && ( value <= minValueInclusive );
            }   // else
        }
        
        #endregion

        #endregion

    }   // class

}   // namespace

9-Year-Old is World's Youngest Microsoft Certified Systems Engineer This is pretty impressive. When I was 9 I was still trying to figure out how I could get Guns N’ Roses to come to my birthday party. Of course the cynics will probably claim it is a reflection of the MCSE certification, but how can you not be impressed?

9-Year-Old is World’s Youngest Microsoft Certified Systems Engineer

via Doug Finke

Note sure how Facebook gets off the hook on this one. This is entirely a *Facebook* problem, not AT&T. If Facebook wasn’t developed by morons with no regard for privacy and security, this bug wouldn’t be possible.

AT&T fixes bug that logged users into random Facebook accounts

Okay, so we were under the impression that Facebook login credentials were a locally-managed affair, but it looks like almost anything can break when AT&T’s involved — according to CNET, the carrier just fixed "several problems" that had users logging into the wrong Facebook account from their phones.

Ed Bott has thrown down the gauntlet when it comes to companies that still use Internet Explorer 6. Users stuck on IE6 are one of the main reasons IE still has such a bad reputation when it comes to security even though Firefox and Safari were found to be far more vulnerable than Internet Explorer in 2009. And according to Ed, allowing people to use IE6 is akin to IT-malpractice.

Any IT professional who is still allowing IE6 to be used in a corporate setting is guilty of malpractice. Think that judgment is too harsh? Ask the security experts at Google, Adobe, and dozens of other large corporations that are cleaning up the mess from a wave of targeted attacks that allowed source code and confidential data to fall into the hands of well-organized intruders. The entry point? According to Microsoft, it’s IE6.

First, upgrade to Internet Explorer 8 if you haven’t already.

Then, read more: It’s time to stop using IE6

There are all sorts of official ways of doing this implementing various IE hosting interfaces in order to handle the presentation of the UI. But if you’re trying to automate a web page and a pesky window.alert is blocking your progress, the following code will supress it for the current page.

/// <summary>
/// Handles the Navigated event of the browser control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="T:WebBrowserNavigatedEventArgs"/> instance containing the 
/// event data.</param>
private void browser_Navigated( object sender, WebBrowserNavigatedEventArgs e )
{

    if ( browser.Document != null ) {

        // kills messagebox functionality by disabling the "window.alert()" function
        object window = browser.Document.Window.DomWindow;
        if ( window != null ) {
            Type windowType = window.GetType();
            BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.Instance;
            string[] args = { "window.alert=function() {};", "JScript" };
            windowType.InvokeMember( "[DispID=1165]", flags, null, window, args );
        }   // if

    }   // if

}

.NET 3.5 brought a bunch of great extension methods to the framework, most of which extended the IEnumerable<T> (or IQueryable<T>) interfaces implemented by just about every collection type. These include simple ones such as Where, OrderBy, FirstOrDefault, etc as well as more complex ones like Aggregate, GroupBy, etc. The Enumerable class really covers a lot of ground, but I’ve created a couple more that I use from time to time in my “Common” assembly.

The extension method I’m writing about today is called Batch. It allows you to take any input sequence (array, collection, iterator, etc.) and process it in batches of a specified size.

Many times you will have an input sequence that contains a lot of items but for various reasons, you can’t process the entire sequence at once (memory constraints, you want to update a console with progress, etc.) and processing one at a time is too slow.

Before, I used to write code that would look like this.

var batch = new List();

foreach (var item in sequence) {

    batch.Add(item);

    if (batch.Count == 100) {
        BulkInsert(batch);
        batch.Clear();
    }

}

This isn’t too bad but it’s a lot of ceremony and extension methods are all about turning globs like that into more readable, concise code. The method signature for the Batch method looks like the following.

/// <summary>
/// Enumerates a sequence in chunks, yielding batches of a certain size to the enumerator.
/// </summary>
/// <typeparam name="T">The type of item in the batch.</typeparam>
/// <param name="sequence">The sequence of items to be enumerated.</param>
/// <param name="batchSize">The maximum number of items to include in a batch.</param>
/// <returns>A sequence of arrays, with each array containing at most
/// <paramref name="batchSize"/> elements.</returns>
public static IEnumerable<T[]> Batch<T>( this IEnumerable<T> sequence, int batchSize )

As the summary suggests, it converts an input sequence into a sequence of arrays of a specified size. The last array in the sequence will have fewer items than the batch size if the input count is not evenly divisible by batchSize. Calling it is very simple. The first example can be rewritten as the following.

foreach (Record[] batch in sequence.Batch(100)) {
    BulkInsert(batch);
}

The complete code example is shown below.

/// <summary>
/// Enumerates a sequence in chunks, yielding batches of a certain size to the enumerator.
/// </summary>
/// <typeparam name="T">The type of item in the batch.</typeparam>
/// <param name="sequence">The sequence of items to be enumerated.</param>
/// <param name="batchSize">The maximum number of items to include in a batch.</param>
/// <returns>A sequence of arrays, with each array containing at most
/// <paramref name="batchSize"/> elements.</returns>
public static IEnumerable<T[]> Batch<T>( this IEnumerable<T> sequence, int batchSize )
{

    var batch = new List<T>( batchSize );

    foreach ( var item in sequence ) {

        batch.Add( item );

        // when we've accumulated enough in the
        // batch, send it out
        if ( batch.Count >= batchSize ) {
            yield return batch.ToArray( );
            batch.Clear( );
        }   // if

    }   // foreach

    // send out any leftovers
    if ( batch.Count > 0 ) {
        yield return batch.ToArray( );
        batch.Clear( );
    }   // if

}

Regular Expressions in .NET are pretty easy to use (assuming you understand the Regex syntax which is beside the case) and certainly you can think of some useful extension methods for System.String that would allow you to quickly validate against a particular regular expression pattern. But regular expressions have another great feature that you maybe don’t use as much – that is the ability to capture subexpressions into groups so that you can pull out a piece of the match.

The way this is done using the Regex class is pretty straightforward.

// Find a word that starts with H and W
string input = "Hello World";
string pattern = @"(H\w+) (W\w+)";

Match m = Regex.Match(input, pattern);
if (m.Success) {
    string hWord = m.Groups[1].Value;
    string wWord = m.Groups[2].Value;
}

This is easy enough, but I am annnoyed by the code that accesses the groups. It seems like such a mundane detail to worry about Group and Match objects. What if the code could be simplified like the following:

string input = "Hello World";
string pattern = @"(H\w+) (W\w+)";

string hWord, wWord;
if (input.MatchInto(pattern, out hWord, out wWord)) ...

It may not look like much of a savings in terms of lines of code, but I find that it looks much cleaner and is a lot less explicit. The full code is below.

/// <summary>
/// Performs a regular expression match against the specified string, and places the captured groups into the output parameters.
/// </summary>
/// <param name="input">The input string to match against.</param>
/// <param name="pattern">The regular expression pattern which should contain grouping expressions.</param>
/// <param name="value1">Receives the value of the 1st capture group (Groups[1]) or null if no match was made.</param>
/// <param name="value2">Receives the value of the 2nd capture group (Groups[2]) or null if no match was made.</param>
/// <param name="value3">Receives the value of the 3rd capture group (Groups[3]) or null if no match was made.</param>
/// <param name="value4">Receives the value of the 4th capture group (Groups[4]) or null if no match was made.</param>
/// <param name="value5">Receives the value of the 5th capture group (Groups[5]) or null if no match was made.</param>
/// <returns>True if the pattern matched (not necessarily all groups) otherwise false.</returns>
public static bool MatchInto( this string input, string pattern, out string value1, out string value2, out string value3, out string value4, out string value5 )
{

    value1 = value2 = value3 = value4 = value5 = null;

    var match = Match( input, pattern );
    if ( match.Success ) {

        // Value1
        if ( match.Groups.Count > 1 && match.Groups[1].Success ) {
            value1 = match.Groups[1].Value;
        }   // if

        // Value2
        if ( match.Groups.Count > 2 && match.Groups[2].Success ) {
            value2 = match.Groups[2].Value;
        }   // if

        // Value3
        if ( match.Groups.Count > 3 && match.Groups[3].Success ) {
            value3 = match.Groups[3].Value;
        }   // if

        // Value4
        if ( match.Groups.Count > 4 && match.Groups[4].Success ) {
            value4 = match.Groups[4].Value;
        }   // if

        // Value5
        if ( match.Groups.Count > 5 && match.Groups[5].Success ) {
            value5 = match.Groups[5].Value;
        }   // if

        return true;

    }   // if

    return false;

}

/// <summary>
/// Performs a regular expression match against the specified string, and places the captured groups into the output parameters.
/// </summary>
/// <param name="input">The input string to match against.</param>
/// <param name="pattern">The regular expression pattern which should contain grouping expressions.</param>
/// <param name="value1">Receives the value of the 1st capture group (Groups[1]) or null if no match was made.</param>
/// <param name="value2">Receives the value of the 2nd capture group (Groups[2]) or null if no match was made.</param>
/// <param name="value3">Receives the value of the 3rd capture group (Groups[3]) or null if no match was made.</param>
/// <param name="value4">Receives the value of the 4th capture group (Groups[4]) or null if no match was made.</param>
/// <returns>True if the pattern matched (not necessarily all groups) otherwise false.</returns>
public static bool MatchInto( this string input, string pattern, out string value1, out string value2, out string value3, out string value4 )
{
    string value5;
    return MatchInto( input, pattern, out value1, out value2, out value3, out value4, out value5 );
}

/// <summary>
/// Performs a regular expression match against the specified string, and places the captured groups into the output parameters.
/// </summary>
/// <param name="input">The input string to match against.</param>
/// <param name="pattern">The regular expression pattern which should contain grouping expressions.</param>
/// <param name="value1">Receives the value of the 1st capture group (Groups[1]) or null if no match was made.</param>
/// <param name="value2">Receives the value of the 2nd capture group (Groups[2]) or null if no match was made.</param>
/// <param name="value3">Receives the value of the 3rd capture group (Groups[3]) or null if no match was made.</param>
/// <returns>True if the pattern matched (not necessarily all groups) otherwise false.</returns>
public static bool MatchInto( this string input, string pattern, out string value1, out string value2, out string value3 )
{
    string value4;
    string value5;
    return MatchInto( input, pattern, out value1, out value2, out value3, out value4, out value5 );
}

/// <summary>
/// Performs a regular expression match against the specified string, and places the captured groups into the output parameters.
/// </summary>
/// <param name="input">The input string to match against.</param>
/// <param name="pattern">The regular expression pattern which should contain grouping expressions.</param>
/// <param name="value1">Receives the value of the 1st capture group (Groups[1]) or null if no match was made.</param>
/// <param name="value2">Receives the value of the 2nd capture group (Groups[2]) or null if no match was made.</param>
/// <returns>True if the pattern matched (not necessarily all groups) otherwise false.</returns>
public static bool MatchInto( this string input, string pattern, out string value1, out string value2 )
{
    string value3;
    string value4;
    string value5;
    return MatchInto( input, pattern, out value1, out value2, out value3, out value4, out value5 );
}

/// <summary>
/// Performs a regular expression match against the specified string, and places the captured groups into the output parameters.
/// </summary>
/// <param name="input">The input string to match against.</param>
/// <param name="pattern">The regular expression pattern which should contain grouping expressions.</param>
/// <param name="value">Receives the value of the 1st capture group (Groups[1]) or null if no match was made.</param>
/// <returns>True if the pattern matched (not necessarily all groups) otherwise false.</returns>
public static bool MatchInto( this string input, string pattern, out string value )
{
    string value2;
    string value3;
    string value4;
    string value5;
    return MatchInto( input, pattern, out value, out value2, out value3, out value4, out value5 );
}

Here’s a helpful tip if you frequently find yourself wrestling with AssemblyInfo.cs (or AssemblyInfo.vb, etc.) when working with a solution with a large number of projects.

I find that most of the time, almost all the information except the AssemblyTitle, AssemblyDescription, and GUID are the same across all projects. Even the GUID you can ignore if you’re not worried about COM visibility.

Just add a SolutionInfo.cs file to the solution (not any project in particular) and put your common assembly details in there. See below for an example.

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[assembly: AssemblyCompany( "Einstein Technologies" )]
[assembly: AssemblyProduct( "My Product" )]
[assembly: AssemblyCopyright( "Copyright 2009 Einstein Technologies. All rights reserved." )]
[assembly: AssemblyTrademark( "" )]
[assembly: AssemblyCulture( "en-US" )]

[assembly: ComVisible( false )]

[assembly: AssemblyVersion( "1.0.*" )]

Next go to each project, right click –> add existing item. Browse to the SolutionInfo.cs file you created above and click the glyph next to the Add button and choose Add as Link.

Now you can reduce your AssemblyInfo.cs file in that project to the lines below and feel confident that everything else is consistent across the projects.

using System.Reflection;

[assembly: AssemblyTitle( "Plugin Framework" )]
[assembly: AssemblyDescription( "A class library that defines plugin interfaces and stuff." )]

Bonus tip: I tend to drag the linked SolutionInfo.cs file into the special “Properties” folder alongside AssemblyInfo.cs. But Visual Studio won’t let you add files directly to this folder via the UI.

Just trying to set up WordPress here. It’s pretty impressive so far.