Vagif Abilov's blog on .NET

April 2011 - Posts

ConstructorInfo.Invoke over Activator.CreateInstance

About a year ago I blogged about different methods of object instantiation and that compiled lambda expressions should be preferred in case same type is instantiated frequently and instantiation performance is critical. However, on many occasions dealing with expression compilation is not worth it, and creating an instance using reflection is simple and usually good enough. Then Activator.CreateInstance seems to be a default choice for such operations. What can be simpler than this call?

var instance = Activator.CreateInstance<SomeType>();

The problem with this approach is its false sense of simplicity: it gives an impression that the instance will be created without any additional information, for example initialization parameters.

To prove how misleading such simplicity can be you just need to try creating an instance of a string:

var instance = Activator.CreateInstance<string>();

The line above causes MissingMethodException with message “No parameterless constructor defined for this object”. And of course, System.String does not have a default constructor.

So what to do? We can of course say that if a class does not have default constructor, then instantiating it in such way is a bad idea, and those classes are just not intended to be used with code trying to create class instances in such manner. However, I bet that more often than not the semantics behind the code “Activator.CreateInstance<T>” is “give me some instance of T, I don’t care about how you create it”. Under such conditions invoking an arbitrary class constructor is preferred over dealing with MissingMethodException. So why not using ConstructorInfo.Invoke then?

Of course, once you settle for ConstructorInfo, you have to decide what constructor overload to use. But this is not really different from choosing parameterless overload of Activator.CreateInstance. In many cases you don’t choose default constructor because this the state of the object you need – you choose it because you don’t care about the state and thus would like to run the simplest form of creation. Applying the same logic to classes without default constructor we can choose a constructor overload with fewest number of parameters:

var constructor = typeof(T).GetConstructors().OrderBy(c => c.GetParameters().Length).FirstOrDefault();
if (constructor == null)
    throw new MissingMethodException("No public constructor defined for this object");
var instance = constructor.Invoke(new object[constructor.GetParameters().Length]);

One area when this approach can be useful is if there is a need to create an instance of an anonymous type (I know it’s a very special need – why on earth will you want to create an instance of an anonymous type? – but I’ve come across a situation):

var o = new { A = "a", B = 1 };
var constructor = o.GetType().GetConstructors()[0];
var instance = constructor.Invoke(new object[constructor.GetParameters().Length]);

Anonymous types don’t get a parameterless constructor, but there is a constructor with a number of arguments equal to the number of type’s properties. I would however use syntax GetConstructors().OrderBy(c => c.GetParameters().Length).FirstOrDefault() that can be applied in general case.

Enhancing SpecFlow set comparison methods

SpecFlow has an excellent set of Table extension methods contributed by Darren Cauthon (he also made a video available on TekPub). Methods such as CreateInstance, CreateSet or CompareSet enable conversion of SpecFlow tables to strong-typed item collections and comparing tables to IEnumerable generic types. Here is an example of such comparison.

First we create a feature scenario:

Scenario: Match
When I have a collection
| Artist | Album |
|
Beatles | Rubber Soul |
|
Pink Floyd | Animals |
|
Muse | Absolution |
Then it should match
| Artist | Album |
|
Beatles | Rubber Soul |
|
Pink Floyd | Animals |
|
Muse | Absolution |

And here are the step definitions:

[When(@"I have a collection")]
public void WhenIHaveACollection(Table table)
{
    var collection = table.CreateSet();
    ScenarioContext.Current.Add("Collection", collection);
}

[Then(@"it should match")]
public void ThenItShouldMatch(Table table)
{
    var collection = ScenarioContext.Current["Collection"] as IEnumerable;
    table.CompareToSet(collection);
}

Use of CompareToSet is a great time-saver, but it current implementation (1.6) covers only collection equivalence: two collections are considered to be equivalent if they have the same items without regard for order. But there may be scenarios when collections need to be compared differently. If you check MsTest helper class CollectionAssert, you will find methods like IsSubsetOf, Contains, DoesNotContain etc. Comparison criteria can be divided in two groups, with four modes in each. Positive comparison group include the following criteria:

  • Equivalence (same items in both collections, order is insignificant, corresponds to CompareToSet implementation in SpecFlow 1.6 and earlier versions);
  • Equality (same items in the same order);
  • Subset (one collection is a subset of another collection);
  • Intersection (collections have common items).

Negative comparison group consists of the same comparison criteria, but in negated form: not equivalent, not equal, not subset and empty intersection.

CompareToSet<T> can be extended with these criteria, it just needs extra optional parameters and enumerator type that lists comparison modes:

public enum TableComparison
{
    TheTableAndSetMatchWithoutRegardForOrder,
    TheTableAndSetAreAnExactMatch,
    AllItemsInTheTableCanBeFoundWithinTheSet,
    TheTableAndSetHaveCommonItems
}

public static void CompareToSet(this Table table, IEnumerable set, 
    TableComparison comparisonMode = TableComparison.TheTableAndSetMatchWithoutRegardForOrder, 
    bool expectedMatch = true)

Calling CompareToSet without specifying optional parameters will result in test for equivalence, while specifying explicit enumerator value can choose other criteria. In addition, passing “false” ad expectedMatch will assert for comparison failure. Here are examples of using enhanced version of CompareToSet:

Feature: Table comparison
In order to avoid writing my own collection comparison code
As a SpecFlow user
I want to compare collections using different criteria

Scenario: Match
When I have a collection
| Artist | Album |
|
Beatles | Rubber Soul |
|
Pink Floyd | Animals |
|
Muse | Absolution |
Then it should match
| Artist | Album |
|
Beatles | Rubber Soul |
|
Pink Floyd | Animals |
|
Muse | Absolution |
And it should match
| Artist | Album |
|
Beatles | Rubber Soul |
|
Muse | Absolution |
|
Pink Floyd | Animals |
And it should exactly match
| Artist | Album |
|
Beatles | Rubber Soul |
|
Pink Floyd | Animals |
|
Muse | Absolution |
But it should not match
| Artist | Album |
|
Beatles | Rubber Soul |
|
Queen | Jazz |
|
Muse | Absolution |
And it should not match
| Artist | Album |
|
Beatles | Rubber Soul |
|
Muse | Absolution |
And it should not exactly match
| Artist | Album |
|
Beatles | Rubber Soul |
|
Muse | Absolution |
|
Pink Floyd | Animals |

Scenario: Containment
When I have a collection
| Artist | Album |
|
Beatles | Rubber Soul |
|
Pink Floyd | Animals |
|
Muse | Absolution |
Then it should contain all items
| Artist | Album |
|
Beatles | Rubber Soul |
|
Muse | Absolution |
But it should not contain all items
| Artist | Album |
|
Beatles | Rubber Soul |
|
Muse | Resistance |
And it should not contain any of items
| Artist | Album |
|
Beatles | Abbey Road |
|
Muse | Resistance |

And here are the step definitions:

[Binding]
public class StepDefinitions
{
    [When(@"I have a collection")]
    public void WhenIHaveACollection(Table table)
    {
        var collection = table.CreateSet();
        ScenarioContext.Current.Add("Collection", collection);
    }

    [Then(@"it should match")]
    public void ThenItShouldMatch(Table table)
    {
        Compare(table);
    }

    [Then(@"it should exactly match")]
    public void ThenItShouldExactlyMatch(Table table)
    {
        Compare(table, TableComparison.TheTableAndSetAreAnExactMatch);
    }

    [Then(@"it should not match")]
    public void ThenItShouldNotMatch(Table table)
    {
        Compare(table, TableComparison.TheTableAndSetMatchWithoutRegardForOrder, false);
    }

    [Then(@"it should not exactly match")]
    public void ThenItShouldNotExactlyMatch(Table table)
    {
        Compare(table, TableComparison.TheTableAndSetAreAnExactMatch, false);
    }

    [Then(@"it should contain all items")]
    public void ThenItShouldContainAllItems(Table table)
    {
        Compare(table, TableComparison.AllItemsInTheTableCanBeFoundWithinTheSet);
    }

    [Then(@"it should not contain all items")]
    public void ThenItShouldNotContainAllItems(Table table)
    {
        Compare(table, TableComparison.AllItemsInTheTableCanBeFoundWithinTheSet, false);
    }

    [Then(@"it should not contain any of items")]
    public void ThenItShouldNotContainAnyOfItems(Table table)
    {
        Compare(table, TableComparison.TheTableAndSetHaveCommonItems, false);
    }

    private void Compare(Table table, TableComparison comparisonMode = TableComparison.TheTableAndSetMatchWithoutRegardForOrder, bool expectedMatch = true)
    {
        var collection = ScenarioContext.Current["Collection"] as IEnumerable;
        table.CompareToSet(collection, comparisonMode, expectedMatch);
    }
}

Currently this extensions are a proposition that I created in a forked branch of SpecFlow, available here.