April 24, 2009

How to test equality of objects of generic type in C#

Testing two objects for equality seems to be a very easy thing. If you try to write a generic method that does that, your first try might look like this:


public static bool AreEqual<T>(T param1, T param2)
{
return param1 == param2;
}

But it turns out that it doesn't compile:

Operator '==' cannot be applied to operands of type 'T' and 'T'

OK, next try. Why not use the IEquatable<T> interface? Value types implement it so you are not limited to reference types. It could look like this:


public static bool AreEqual<T>(T param1, T param2)
where T: IEquatable<T>
{
return param1.Equals(param2);
}

It compiles (yes! ;-)) and seems to work. But there are problems with it.

Firstly, of course, you will get an exception when param1 is null. In case you think this is easy to solve by testing for null stop reading here and try it yourself before reading on.

You cannot compare it to null as this only works for reference types. Of course instead of null we could use default(T) which returns null for reference types and 0 for value types. But then we again have the problem that the comparison (param1 == default(T)) won't compile. The same error as above...

The second problem is that we have to implement IEquatable<T> for all our classes even if we just want to use reference equality. Not a big deal but still a bit of an annoyance.

But it turns out that the solution to all this is again very simple:

public static bool AreEqual<T>(T param1, T param2)
{
return EqualityComparer<T>.Default.Equals(param1, param2);
}

The static property EqualityComparer<T>.Default returns the default equality comparer for the given type. When T implements IEquatable<T> it returns a comparer that uses that. If not, the default reference equality is used.

Exactly what we need. Not difficult, but you just have to know about it...

April 19, 2009

XAML markup extension with automatic type conversion

I recently had the following problem: I am using a custom XAML markup extension in my WPF application. The markup extension returns strings. While I normally use it for assigning values to string properties I this time wanted to apply it to a different type of property.

The following code illustrates what I wanted to do:


<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:XamlExtAutoConvert="clr-namespace:XamlExtAutoConvert"
x:Class="XamlExtAutoConvert.TestWindow"
Title="TestWindow"
Height="300"
Width="300"
>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">

<Button Width="{XamlExtAutoConvert:Test}" Content="{XamlExtAutoConvert:Test}" />

</Grid>
</Window>


This is of course a pretty stupid example but I hope it gets across what I want to do. Let's assume that the markup extension XamlExtAutoConvert:Test returns the string "200". This string should end up as the button content and at the same time define the width of the button.

As in XML the width of the button is specified as a string too (after all XML is nothing more than strings) and converted to double (which is the type of the Width property) I was hoping the same was happening with the value returned from the markup extension.

I was wrong.

You will get the following exception:


System.Windows.Markup.XamlParseException occurred
Message="'200' value cannot be assigned to property 'Width' of object
'System.Windows.Controls.Button'.
'200' is not a valid value for property 'Width'.


Fortunately there's a way to rewrite the markup extension to cover this use case. You can determine the type of the target property. And with that you can try to convert the value to that type.

A sample markup extension looks like this:


using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Markup;

namespace XamlExtAutoConvert
{
class TestExtension: MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = (IProvideValueTarget)serviceProvider.GetService(
typeof(IProvideValueTarget));
if (target != null)
{
// get the actual value to return
string value = GetValue();

// determine the type we need to convert to
object convertedValue = null;
Type targetType = null;
if (target.TargetProperty is DependencyProperty)
{
var property = target.TargetProperty as DependencyProperty;
targetType = property.PropertyType;
}
else
{
// if needed we could also support other types
}

// check if conversion is needed
if (targetType != null &&
targetType != typeof(string) &&
targetType != typeof(object))
{
// try a conversion
try
{
TypeConverter converter = TypeDescriptor.GetConverter(targetType);
object retValue = converter.ConvertFromString(value);
convertedValue = retValue;
}
catch (NotSupportedException)
{
//fall through
}
}

// if we have a converted value, return it
if (convertedValue != null)
return convertedValue;

// else just return the string value
return value;
}
return null;
}

private string GetValue()
{
// replace with whatever the extension is supposed to do
return "200";
}
}
}