Dennis Burton's Develop Using .NET

Change is optional. Survival is not required.
Tags: programming

Predicate: Logic. that which is affirmed or denied concerning the subject of a proposition.

In programming terms, a function that returns true or false taking a subject as a parameter. So, Predicate<T> is really just a specific signature applied to a delegate. A method compliant with this signature looks like:

bool method(T item)

The collection classes in System.Collections.Generic have several methods that will take a predicate as a parameter. I really like this separation of concerns. The responsibility of iterating through the collection is owned by the collection; the responsibility of making a decision based on data in an object is owned by the object. What seemed to be missing to me was the common operation of combining boolean results with logical operators. What I really wanted to do was to pass a set of predicate functions to the collection methods. Of course, the collection authors could not know how I would want the boolean results combined. This seemed to be the responsibility of something else that could contain a list of predicates, but at the same time return a predicate function that is the logical combination of the results.

The PredicateList class is a container of Predicate<T> delegates. The methods that can be used as a predicate are the logical AND and OR operations. These methods iterate through the contained predicates and short circuit when arriving at a value that determines the result of the operation.

using System;
using System.Collections.Generic;

namespace Utilities
{
  public class PredicateList<T> : List<Predicate<T>>
  {
    public PredicateList(params Predicate<T>[] predicates)
    {  AddRange(predicates);  }
    public bool And(T item)
    {
      foreach (Predicate<T> pred in this)
      {
        if (!pred(item)) return false;
      }
      return true;
    }
    public bool Or(T item)
    {
      foreach (Predicate<T> pred in this)
      {
        if (pred(item)) return true;
      }
      return false;
    }
  }
}

Using this class with one of the collection methods would look like:

PredicateList<T> filters = new PredicateList<T>(IsThis, IsThat, IsTheOtherThing);
return FindAll(filters.And);

Tags: tdd

While working on the tests for the next post, I was reminded of how cool MbUnit can be. Most tests have the signature of public void TestMethod(). In some cases, this causes a propagation of tests in order to accomplish running pretty much the same test with different data. MbUnit adds the RowTest attribute to the mix. With the RowTest and Row attributes you can create one parameterized test "template" and the test will run for each data item provided. Running the following code will execute 3 different tests, one for each of the Row parameters provided.

[RowTest]
[Row(2,8)]
[Row(5,5)]
[Row(8,2)]
public void GreaterValueTest(int filterValue, int expectedCount)
{
  ClassicCollectionBase results = col.FilteredCollection(new GreaterValueFilter(filterValue));
  Assert.AreEqual(expectedCount, results.Count);
}

This cool feature can help reduce the maintenance time associated with test code.

What about NUnit?

With the new features of NUnit 2.4 Andreas Schlapsi has an NUnit extension that allows for RowTests. I would expect this exceptional feature to get rolled into the core in an upcoming release.

With the release of VS 2008 and SQL Server 2008, a set of certification updates are in the works. If you are one to keep your certifications up to date, you may be interested in the following webcasts:

MCP Live Meeting: Microsoft Certification for Developers

MCP Live Meeting: SQL Server 2008 and Your Microsoft Certifications

UPDATE: These were live wecasts on Jan 23 2008. They have not yet been archived for On-Demand use.

Tags: fundamentals

Equality comparisons in .net behave differently by default for reference types and value types. Value types are considered equal when the state of the object is equal. Reference types are considered equal when the reference points to the same location in memory. Therefore two different instances of a reference type would not be considered equal even if the internal state is the same. A quick unit test can be used to show this.

The class we will be using for comparison:

using System;

namespace Equality
{
  class MyClass
  {
    public MyClass(int id)
    { _id = id; }

    private int _id;
    public int Id
    {
      get { return _id; }
      set { _id = value; }
    }
  }
}

The test fixture and the first test:

using System;
using MbUnit.Framework;

namespace Equality.Test
{
  [TestFixture]
  class EqualityTest
  {
    const int equalId = 10;
    const int notEqualId = 12;

    MyClass testClass = new MyClass(equalId);
    MyClass equalClass = new MyClass(equalId);
    MyClass notEqualClass = new MyClass(notEqualId);

    [Test]
    public void EqualsMethod_Test()
    {
      Assert.IsTrue(testClass.Equals(equalClass),"Equal by identity but not by reference");
      Assert.IsFalse(testClass.Equals(notEqualClass), "Not equal by either identity or reference");
    }
  }
}

Test Results:

------ Test started: Assembly: Equality.exe ------
Starting the MbUnit Test Execution
Exploring Equality, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
MbUnit 1.0.2700.29885 Addin
Found 1 tests
[failure] EqualityTest.EqualsMethod_Test
TestCase 'EqualityTest.EqualsMethod_Test' failed: Equal by identity but not by reference
MbUnit.Core.Exceptions.AssertionException
Message: Equal by identity but not by reference
...
0 passed, 1 failed, 0 skipped, took 1.63 seconds.

Since all reference types derive from System.Object, we can change this behavior by overriding the Equals method to do a comparison that means something to the object. In this case we will use the Id property. In this case we will first test to make sure that the object being compared is of the correct type and then we will compare the Id property. As an interesting excercise, take a look at the override of the Equals method in the System.ValueType class using Reflector. This implementation uses reflection to determine what items can hold state. It then does a comparison for each item (using .Equals in the end so that reference types work correctly).

  public override bool Equals(object obj)
  {
    MyClass testClass = obj as MyClass;
    return Id == testClass.Id;
  }

Results:

------ Test started: Assembly: Equality.exe ------
Starting the MbUnit Test Execution
Exploring Equality, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
MbUnit 1.0.2700.29885 Addin
Found 1 tests
[success] EqualityTest.EqualsMethod_Test
1 passed, 0 failed, 0 skipped, took 1.59 seconds.

Now we have the Equals method expressing a meaning for equality that matches our understanding of the object. This is a nice advantage when using framework classes such as collections. Collection methods like Contains will call the object's Equals method. Simply overriding the Equals method gives us many more tools to work with without much effort. One thing that should be mentioned here is the warning that occurs when you override Equals. You will be greeted with a compiler warning about overriding Equals without overriding GetHashCode. GetHashCode is defined as a method that should return a number unique for a given instance of the object. We are using the Id property for that purpose.

public override int GetHashCode()
{ return Id; }

Now it would be really nice to have some consitancy with other mechanims used to compare equality. Another common mechanism for equality testing is the == operator. Lets put together a test that will see if this complies with our objects definition of equality.

[Test]
public void EqualOperator_Test()
{
  Assert.IsTrue(testClass == equalClass, "Equal by identity but not by reference");
  Assert.IsFalse(testClass == notEqualClass, "Not equal by either identity or reference");
}

Results:

------ Test started: Assembly: Equality.exe ------
Starting the MbUnit Test Execution
Exploring Equality, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
MbUnit 1.0.2700.29885 Addin
Found 2 tests
[success] EqualityTest.EqualsMethod_Test
[failure] EqualityTest.EqualOperator_Test
TestCase 'EqualityTest.EqualOperator_Test' failed: Equal by identity but not by reference
Message: Equal by identity but not by reference
...
1 passed, 1 failed, 0 skipped, took 1.78 seconds.


 

Looks like we have a bit more work to do. What we need to do now is provide the operator == method. We will take advantage of the fact that we have already defined equality in the Equals method on our object. (Note that this is a static method that takes two objects as parameters. One side effect of this is that you do not get to make static members virtual and override them.)

public static bool operator==(MyClass lhs, MyClass rhs)
{
return lhs.Equals(rhs); }

Now we fire up the test to see where we stand and we are greeted with an error message. The compiler is telling us that when == is implemented that != is required as well. After you finish wondering why someone by default would not implement a != method as an inverse of the == method, implement the != method using our Equals method or the == operator. Of course we need to add some tests to ensure that != is doing what we expect.

[Test]
public void NotEqualOperator_EqualTest()
{
  Assert.IsTrue(testClass != notEqualClass, "Not equal by either identity or reference");
  Assert.IsFalse(testClass != equalClass, "Equal by identity but not reference");
}

public static bool operator !=(MyClass lhs, MyClass rhs)
{ return !lhs.Equals(rhs); }

It can be handy for users of your classes to be able to count on basic functionality such as equality. It also will help leverage the framework for collection operations as well. Another thing very similar in nature to this exercise that will help leverage the framework is implementing the IComparable or IComparable<T> interface.

Other Considerations

In Bill Wagner's book Effective C#, Item 10 mentions some issues with overriding GetHashCode. In short, the framework has a very efficient way to come up with the hash code, in many cases you will not come up with something better. If you do not have something as simple as an identity column that is assigned from the database, consider this advice carefully.

 

Tags:

For it seems like forever, friends and colleges have tried to push me into the exercise of blogging. For it seems like forever, I have resisted because I am horribly slow at writing. Two things are finally driving me to get started. As organizational changes have taken place, the company website is much more focused on the credibility of the Business Development staff and not so much on the Technical Staff. So, I no longer have a professional profile on the company web site. That profile was close to 5 years out of date anyway. More importantly, I hope to improve myself by learning more deeply in order to write about it.

This is my entry point into creating an up to date professional profile as well as getting better at the writing process. The technical content will probably not be real deep for a little while as I work through the process of writing. Hopefully this will be a fun ride.

Dennis Burton

View Dennis Burton's profile on LinkedIn
Follow me on twitter
Rate my presentations
Google Code repository

Community Events

Windows Azure Boot Camp Lansing GiveCamp