The beauty of fluent interfaces

May 15, 2008 at 11:43 PMAndre Loker

I notice that more and more frameworks, toolkits and libraries use fluent interfaces. Here are two examples:

Rhino Mocks

   1: [Test]
   2: public void SomeTest() {
   3:     var mocks = new MockRepository();
   4:     var service = mocks.CreateMock<IAccountService>();
   5:     using (mocks.Record()) {
   6:         Expect
   7:             .Call(service.Login(null, null))
   8:             .Constraints(Is.NotNull() & Is.NotSame("foo"), Is.NotNull())
   9:             .Repeat.AtLeastOnce()
  10:             .Return(LoginResult.Failed);
  11:         // ...
  12:     }
  13: }

Castle Windsor

   1: var container = new WindsorContainer();
   2: container.Register(
   3:     Component
   4:         .For<IAppContext>()
   5:         .ImplementedBy<AppContext>()
   6:         .Parameters(Parameter.ForKey("synchronized").Eq("true")));

[Maybe its not surprising that both libraries use fluent interfaces. Ayende Rahien, creator of Rhino Mocks, is also a huge contributor the the Castle project. ]

Most of the time I really like fluent interfaces. Not only does the code reveal its intention more clearly - at least most of the time. You also get support while writing the code: depending on the FI, the object returned by a call to an FI method might only a reduced set of methods that are meaningful at that point which drives you more into the correct direction.

If you write a FI for a library yourself, be sure to not overdo it. Keep it simple and don't try to get everything FI-esque just because you know how to do it. One possible pitfall of fluent interfaces is that it is not always clear how many times a method should be called. For example, imagine the case where you have a Client class that can have multiple order Objects, each containing multiple OrderLine objects. This resembles the example of Martin Fowler. You decide to provide an FI for the operation of adding configuring an order, like so:

   1: var c = new Client();
   2: c.AddOrder()
   3:     .WithLine(10, "Apple", 0.49m)
   4:     .WithLine(4, "Banana", 0.59m)
   5:     .ExpressDelivery();

This looks certainly more fluent than the "conventional" way:

   1: var client = new Client();
   2: var order = new Order();
   3: order.ExpressDelivery = true;
   4: var line = new OrderLine();
   5: line.Quantity = 10;
   6: line.UnitCost = 0.49m;
   7: line.Product = "Apple";
   8: order.AddLine(line);
   9:  
  10: line = new OrderLine();
  11: line.Quantity = 5;
  12: line.UnitCost = 0.59m;
  13: line.Product = "Banana";
  14: order.AddLine(line);
  15:  
  16: client.AddOrder(order);

However, in the FI it is not that obvious that ExpressDelivery() should be called only once. Maybe the user wants to deliver the order twice (ok, the example is bad). The non-fluent version does not have this potential confusion. No one would assign order.ExpressDelivery multiple times and expect it to generate multiple deliveries.

Still, I think this is a small price to pay compared with the improved readability gained by FI.

Of course, the non-FI example is a worst case scenario. For example, you could provide constructor arguments to OrderLine. With C# 3.0 and object/collection initializers the example can be approved even more:

   1: var client = new Client();
   2: var order = new Order {
   3:     new OrderLine {Quantity = 10, UnitCost = 0.49m, Product = "Apple"},
   4:     new OrderLine {Quantity = 5, UnitCost = 0.59m, Product = "Banana"}
   5: };
   6: order.ExpressDelivery = true;
   7: client.AddOrder(order);

I even find the object initializer more expressive than the WithLine FI. I'm really looking forward to all the future APIs that will leverage fluent interfaces and C# 3.0  syntax features.

Posted in: C# | Snippets

Tags: , ,

Anonymous type to dictionary using DynamicMethod

May 3, 2008 at 12:24 PMAndre Loker

C# 3.0 offers a variety of new language features like anonymous types, object and collection initializers and extension methods. With some creativity these features can be used to reduce the amount of code being written and to make the code more readable. Imaging you have a method that expects a dictionary of configuration values, like:

   1: public class MyClass {
   2:     public void DoStuff(IDictionary<string, object> dictionary) {
   3:         // .. do something
   4:     }
   5: }

A typical .NET 2 client would use the method like this:

   1: public static void DotNet2(MyClass mc) {
   2:     Dictionary<string, object> dictionary = new Dictionary<string, object>();
   3:     dictionary["date"] = new DateTime(1970, 6, 12);
   4:     dictionary["foo"] = 123;
   5:     dictionary["name"] = "Some text";
   6:     mc.DoStuff(dictionary);
   7: }

C# 3.0 adds the collection initializer syntax, which reduces the repetitive code parts a lot:

   1: public static void CollectionInitializer(MyClass mc) {
   2:     var dictionary = new Dictionary<string, object> {
   3:         {"date", new DateTime(1970, 6, 12)},
   4:         {"foo", 123},
   5:         {"name", "Some text"}
   6:     };
   7:     mc.DoStuff(dictionary);
   8: }

Currently Microsoft is working on ASP.NET MVC, an MVC implementation for ASP.NET comparable to Ruby On Rails or Castle MonoRail. To make the code more compact - especially in the HTML views - they use anonymous objects instead of dictionaries. In our example a first approach could look like this:

   1: public static void AnonymousWithReflection(MyClass mc) {
   2:     var data = new {
   3:         date = new DateTime(1970, 6, 12),
   4:         foo = 123,
   5:         name = "Some text"
   6:     };
   7:     var dictionary = ObjectHelper.TurnObjectIntoDictionary(data);
   8:     mc.DoStuff(dictionary);
   9: }

This looks a lot cleaner to me. Certainly the first question is: how do we convert the object into a dictionary? A simple implementation could look like this:

   1: public static class ObjectHelper {
   2:     public static IDictionary<string, object> TurnObjectIntoDictionary(object data) {
   3:         var attr = BindingFlags.Public | BindingFlags.Instance;
   4:         var dict = new Dictionary<string, object>();
   5:         foreach (var property in data.GetType().GetProperties(attr)) {
   6:             if (property.CanRead) {
   7:                 dict.Add(property.Name, property.GetValue(data, null));
   8:             }
   9:         }
  10:         return dict;
  11:     }
  12: }

This method uses reflection to grab the values of all readable properties and puts them into a dictionary. Not very difficult to understand, but because of the reflection certainly not the fastest thing in the world as we will see later.

Intermezzo: using extension methods to improve readability

Before we continue our journey lets see whether we can improve the API. The call to ObjectHelper.TurnObjectIntoDictionary looks quite verbose to me, so lets add an extension method to ObjectHelper:

   1: public static IDictionary<string, object> ToDictionaryR(this object obj) {
   2:     return TurnObjectIntoDictionary(obj);
   3: }

I named the method ToDictionaryR to remind me that it uses reflection. This is not meant for production code, just for our little prove of concept here. While the code is not spectacular it improves usability:

   1: public static void AnonymousWithReflection2(MyClass mc) {
   2:     var data = new {
   3:        date = new DateTime(1970, 6, 12),
   4:        foo = 123,
   5:        name = "Some text"
   6:    };
   7:     mc.DoStuff(data.ToDictionaryR());
   8: }

We could tweak the whole thing into another direction by providing an overload of DoStuff that accepts an object which is converted to a dictionary as needed. If we deal with legacy code, we might as well use extension methods again:

   1: public static class MyClassExtensions {
   2:     public static void DoStuff(this MyClass client, object data) {
   3:         if (data is IDictionary<string, object>){
   4:             client.DoStuff((IDictionary<string, object>)data);
   5:         } else {
   6:             client.DoStuff(data.ToDictionaryR());
   7:         }
   8:     }
   9: }

And here the client that uses the extension:

   1: public static void AnonymousWithReflection3(MyClass mc) {
   2:     var data = new {
   3:        date = new DateTime(1970, 6, 12),
   4:        foo = 123,
   5:        name = "Some text"
   6:    };
   7:     mc.DoStuff(data);
   8: }

Looks neat! I do like the compactness of the new API. Let us see how this performs at runtime.

Give me numbers!

As mentioned earlier this approach uses a lot of reflection. This is bad, or isn't it? Benchmarks to the rescue! Here comes the almighty benchmarking program:

   1: private static void Main(string[] args) {
   2:     var mc = new MyClass();
   3:     for (int i = 0; i < 5; ++i) {
   4:         Benchmark("DotNet2", DotNet2, mc);
   5:         Benchmark("CollectionInitializer", CollectionInitializer, mc);
   6:         Benchmark("AnonymousWithReflection3", AnonymousWithReflection3, mc);
   7:     }
   8: }
   9:  
  10: public static void Benchmark(string name, Action<MyClass> exec, MyClass mc) {
  11:     Console.Out.Write("Benchmarking {0,-30}:", name);
  12:     const int count = 100000;
  13:     var sw = new Stopwatch();
  14:     sw.Start();
  15:     for (var i = 0; i < count; ++i) {
  16:         exec(mc);
  17:     }
  18:     sw.Stop();
  19:     Console.Out.WriteLine("{0} ms", sw.ElapsedMilliseconds);
  20: }

We simply invoke the approaches presented before one million times (and that 5 times in a row to let the values settle down) and see how fast they are. Certainly this is not very representative for code in practice. Still I want to get a rough estimate how expensive all that reflection mumbo jumbo really is. So, here are the results on my machine (Intel E6750, 4GB RAM, Vista Ultimate 64):

   1: Benchmarking DotNet2                       :32 ms
   2: Benchmarking CollectionInitializer         :29 ms
   3: Benchmarking AnonymousWithReflection3      :1822 ms
   4: Benchmarking DotNet2                       :28 ms
   5: Benchmarking CollectionInitializer         :29 ms
   6: Benchmarking AnonymousWithReflection3      :1783 ms
   7: Benchmarking DotNet2                       :28 ms
   8: Benchmarking CollectionInitializer         :28 ms
   9: Benchmarking AnonymousWithReflection3      :1789 ms
  10: Benchmarking DotNet2                       :28 ms
  11: Benchmarking CollectionInitializer         :28 ms
  12: Benchmarking AnonymousWithReflection3      :1852 ms
  13: Benchmarking DotNet2                       :29 ms
  14: Benchmarking CollectionInitializer         :29 ms
  15: Benchmarking AnonymousWithReflection3      :1809 ms

The DotNet2 and the CollectionInitializer version perform equally. This was expected, as collection initializers are syntactic sugar only. But the reflection version does bad, I mean REALLY bad: it is roughly 60 times slower than the non-reflective approach.

DynamicMethod to the rescue

Luckily we can minimize the overhead. Instead of using reflection all the time we convert the object we will use reflection one time to create code on the fly that can be reused afterwards. With the DynamicMethod class, this is rather easy. You only have to understand IL (intermediate language). If you have no experience with IL, grab Reflector and poke around in existing code. You'll get the idea.

Here is the code that creates a delegate that will convert an object to a dictionary:

   1: public static Func<object, IDictionary<string, object>> CreateObjectToDictionaryConverter(Type itemType) {
   2:     var dictType = typeof (Dictionary<string, object>);
   3:  
   4:     // setup dynamic method
   5:     // Important: make itemType owner of the method to allow access to internal types
   6:     var dm = new DynamicMethod(string.Empty, typeof (IDictionary<string, object>), new[] {typeof (object)}, itemType);
   7:     var il = dm.GetILGenerator();
   8:  
   9:     // Dictionary.Add(object key, object value)
  10:     var addMethod = dictType.GetMethod("Add");
  11:  
  12:     // create the Dictionary and store it in a local variable
  13:     il.DeclareLocal(dictType);
  14:     il.Emit(OpCodes.Newobj, dictType.GetConstructor(Type.EmptyTypes));
  15:     il.Emit(OpCodes.Stloc_0);
  16:  
  17:     var attributes = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy;
  18:     foreach (var property in itemType.GetProperties(attributes).Where(info => info.CanRead)) {
  19:         // load Dictionary (prepare for call later)
  20:         il.Emit(OpCodes.Ldloc_0);
  21:         // load key, i.e. name of the property
  22:         il.Emit(OpCodes.Ldstr, property.Name);
  23:  
  24:         // load value of property to stack
  25:         il.Emit(OpCodes.Ldarg_0);
  26:         il.EmitCall(OpCodes.Callvirt, property.GetGetMethod(), null);
  27:         // perform boxing if necessary
  28:         if (property.PropertyType.IsValueType) {
  29:             il.Emit(OpCodes.Box, property.PropertyType);
  30:         }
  31:  
  32:         // stack at this point
  33:         // 1. string or null (value)
  34:         // 2. string (key)
  35:         // 3. dictionary
  36:  
  37:         // ready to call dict.Add(key, value)
  38:         il.EmitCall(OpCodes.Callvirt, addMethod, null);
  39:     }
  40:     // finally load Dictionary and return
  41:     il.Emit(OpCodes.Ldloc_0);
  42:     il.Emit(OpCodes.Ret);
  43:  
  44:     return (Func<object, IDictionary<string, object>>) dm.CreateDelegate(typeof (Func<object, IDictionary<string, object>>));
  45: }

The result of this method is a delegate that expects an object and returns a dictionary. Invoking CreateObjectToDictionaryConverter is rather expensive, so lets cache the result by providing a factory/registry:

   1: /// <summary>
   2: /// Loads the values of an object's properties into a <see cref="IDictionary{String,Object}"/>
   3: /// </summary>
   4: public class ObjectToDictionaryRegistry {
   5:     private static readonly Dictionary<Type, Func<object, IDictionary<string, object>>> cache = new Dictionary<Type, Func<object, IDictionary<string, object>>>();
   6:     private static readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
   7:  
   8:     /// <summary>
   9:     /// Loads the values of an object's properties into a <see cref="IDictionary{String,Object}"/>.
  10:     /// </summary>
  11:     /// <param name="dataObject">The data object.</param>
  12:     /// <returns>If <paramref name="dataObject"/> implements <see cref="IDictionary{String,Object}"/>, 
  13:     /// the object is cast to <see cref="IDictionary{String,Object}"/> and returned.
  14:     /// Otherwise the object returned is a <see cref="System.Collections.Hashtable"/> with all public non-static properties and their respective values
  15:     /// as key-value pairs.
  16:     /// </returns>
  17:     public static IDictionary<string, object> Convert(object dataObject) {
  18:         if (dataObject == null) {
  19:             return null;
  20:         }
  21:         if (dataObject is IDictionary<string, object>) {
  22:             return (IDictionary<string, object>) dataObject;
  23:         }
  24:         return GetObjectToDictionaryConverter(dataObject)(dataObject);
  25:     }
  26:  
  27:     /// <summary>
  28:     /// Handles caching.
  29:     /// </summary>
  30:     /// <param name="item">The item.</param>
  31:     /// <returns></returns>
  32:     private static Func<object, IDictionary<string, object>> GetObjectToDictionaryConverter(object item) {
  33:         rwLock.EnterUpgradeableReadLock();
  34:         try {
  35:             Func<object, IDictionary<string, object>> ft;
  36:             if (!cache.TryGetValue(item.GetType(), out ft)) {
  37:                 rwLock.EnterWriteLock();
  38:                 // double check
  39:                 try {
  40:                     if (!cache.TryGetValue(item.GetType(), out ft)) {
  41:                         ft = CreateObjectToDictionaryConverter(item.GetType());
  42:                         cache[item.GetType()] = ft;
  43:                     }
  44:                 } finally {
  45:                     rwLock.ExitWriteLock();
  46:                 }
  47:             }
  48:             return ft;
  49:         } finally {
  50:             rwLock.ExitUpgradeableReadLock();
  51:         }
  52:     }
  53:  
  54:     private static Func<object, IDictionary<string, object>> CreateObjectToDictionaryConverter(Type itemType) {
  55:         // as seen above
  56:     }
  57: }

There is nothing fancy going on. This implementation already has added synchronization for thread-safety which adds a little complexity. Note by the way that I am using the new ReaderWriterLockSlim. I expect much more reads from the cache than writes to it so this seems to be a reasonable choice. I also added the check to see whether the object provided to Convert already is a IDictionary<string, object> in case of which it is simply cast.

Given this new implementation we change the usage a bit:

   1: // modified version of the MyClass extension method
   2: public static class MyClassExtensions {
   3:     public static void DoStuff(this MyClass client, object data) {
   4:         client.DoStuff(ObjectToDictionaryRegistry.Convert(data));
   5:     }
   6: }
   7:  
   8: // our final testcase using ObjectToDictionaryRegistry
   9: public static void Final(MyClass mc) {
  10:     mc.DoStuff(new {
  11:            date = new DateTime(1970, 6, 12),
  12:            foo = 123,
  13:            name = "Some text"
  14:        });
  15: }

So, let us see how this performs. Adding the Final method to our benchmarking set, we get the following results:

   1: Benchmarking DotNet2                       :41 ms
   2: Benchmarking CollectionInitializer         :33 ms
   3: Benchmarking AnonymousWithReflection2      :1961 ms
   4: Benchmarking Final                         :60 ms
   5: Benchmarking DotNet2                       :27 ms
   6: Benchmarking CollectionInitializer         :28 ms
   7: Benchmarking AnonymousWithReflection2      :1791 ms
   8: Benchmarking Final                         :58 ms
   9: Benchmarking DotNet2                       :31 ms
  10: Benchmarking CollectionInitializer         :29 ms
  11: Benchmarking AnonymousWithReflection2      :1886 ms
  12: Benchmarking Final                         :50 ms
  13: Benchmarking DotNet2                       :30 ms
  14: Benchmarking CollectionInitializer         :28 ms
  15: Benchmarking AnonymousWithReflection2      :1914 ms
  16: Benchmarking Final                         :53 ms
  17: Benchmarking DotNet2                       :30 ms
  18: Benchmarking CollectionInitializer         :28 ms
  19: Benchmarking AnonymousWithReflection2      :1827 ms
  20: Benchmarking Final                         :54 ms

Wow, this is cool: the version using DynamicMethod takes only two times longer than the dictionary version. That's highly acceptable given the fact that:

  • We have to do synchronization at the registry.
  • The method actually being called currently does nothing where in practice it will most likely take more time to execute compared to the cost of conversion. The difference between the DynamicMethod approach and the dictionary approach will very soon shrink a lot.

What we gain and what we lose

Personally I think that the syntax using anonymous types is quite nice. We need less (disturbing) characters like quotes, braces etc. to express what we want. The costs are neglectable if we use DynamicMethod and caching.

However, not everyone is happy with this approach. Jeffrey Palermo for example finds that MS makes "a huge mistake" by providing this kind of API for ASP.NET MVC. I think that this kind of API has its place but should - as always - only be used where appropriate. If you now in advance which keys to expect in the dictionary, you'd most likely be better of using a strongly typed object with an object initializer. When the keys cannot be foreseen the API can gain readability by using anonymous types. You certainly do have a problematic API when the parameters provided are that dynamic, but this is not the fault of the anonymous type. Providing string keys plus object values in a dictionary is not typesafe nor intuitive in the first place. Anonymous types can only help to improve the problematic situation.

Posted in: C# | Snippets

Tags: , ,

Where T : Enum?

April 17, 2008 at 6:13 PMAndre Loker

Mir ist nicht klar, warum C# bei den Constraints von generischen Typen und Methoden manchmal so pingelig ist, obwohl die CLR die entsprechenden Constraints erlaubt.

Beispiel: folgender Code ist in C# nicht kompilierbar

   1: class X {
   2:     public void MachWas<T>(T foo) where T : Enum {
   3:     }
   4: }

error CS0702: Constraint cannot be special class 'System.Enum'

In C++/CLI kein Problem:

   1: ref class X {
   2:     generic<typename T> where T : System::Enum
   3:     void MachWas( T foo) {
   4:     }
   5: };

Posted in: Snippets

Tags: , ,

C# 3.0 - Auto Properties

March 19, 2008 at 11:38 AMAndre Loker

C# 3.0 bietet als Neuerung so genannte Auto Properties. Das sind einfache Properties, für der Compiler automatisch ein Backing Field erstellt. Beispiel:

   1: class MyClass {
   2:     // Auto property 
   3:     public int Position { get; set; }
   4: }

Der compiler erstellt ein implizites Backing Field für Position. Möchte man den Setter vor Zugriffen von außen schützen, ist das auch kein Problem:

   1: class MyClass {
   2:     // Auto property
   3:     public int Position { get; private set; }
   4: }

Der Vorteil dieser Syntax ist die kompakte Schreibweise für triviale Properties. Zudem ist es Problemlos möglich, bei Bedarf die Auto Property in eine vollständige Property zu upgraden bei Beibehaltung der Binärkompatibilität.

Ich stelle allerdings nach einiger Zeit fest, dass diese Syntax für den Leser des Quellcodes verwirrend ist. Ich finde es als Entwickler übersichtlicher, wenn alle Felder zu Beginn der Klasse angegeben sind. Wenn ich Auto Properties verwende, fehlt mir die sofortige Orientierung bzgl. der verwendeten Felder, besonders bei der Vermischung von Auto Properties und expliziten Backing Fields. Da Auto Properties nicht gesondert gekennzeichnet werden, muss ich ggf. über alle Properties schauen, um zu sehen, was wirklich in der Klasse vor sich geht. Es ist übrigens nicht möglich, readonly Felder als Auto Properties darzustellen. Bleibt noch zu resümieren, dass ich Auto Properties - außer bei trivialen Klassen - in Zukunft meiden werde.

Posted in: C#

Tags:

Extension methods sind doch irgendwie cool

February 28, 2008 at 2:49 PMAndre Loker

Eigentlich stand ich den Extension Methods von C# 3.0 ja kritisch gegenüber, aber irgendwie sind sie doch ziemlich cool :-)

Hier ein kleines Beispiel aus der Praxis.

   1: /// <summary>
   2: /// Provides extension methods for <see cref="string"/>
   3: /// </summary>
   4: public static class StringExtensions {
   5:     /// <summary>
   6:     /// Calls ToString on the given object if <paramref name="obj"/> is not null; otherwise, 
   7:     /// <see cref="string.Empty"/> is returned.
   8:     /// </summary>
   9:     /// <param name="obj">The obj.</param>
  10:     /// <returns>The result of <see cref="object.ToString()"/> invoked on <paramref name="obj"/> if
  11:     /// <paramref name=" obj"/> is not null; otherwise, <see cref="string.Empty"/></returns>
  12:     public static string ToSafeString<T>( this T obj ) {
  13:         if ( typeof( T ).IsValueType ) {
  14:             return obj.ToString();
  15:         }
  16:         return Equals( obj, null ) ? string.Empty : obj.ToString();
  17:     }
  18:  
  19:     /// <summary>
  20:     /// Formats a string using the given format and a value.
  21:     /// </summary>
  22:     /// <param name="format">The format.</param>
  23:     /// <param name="value">The value.</param>
  24:     /// <returns>The formatted string</returns>
  25:     public static string Formatted<T>( this string format, T value )
  26:         where T : struct {
  27:         return string.Format( format, value.ToSafeString() );
  28:     }
  29:  
  30:     /// <summary>
  31:     /// Formats a string using the given format and the given values
  32:     /// </summary>
  33:     /// <typeparam name="T1">The type of the first argument.</typeparam>
  34:     /// <typeparam name="T2">The type of the second argument.</typeparam>
  35:     /// <param name="format">The format.</param>
  36:     /// <param name="value1">The first value.</param>
  37:     /// <param name="value2">The second value.</param>
  38:     /// <returns>The formatted string</returns>
  39:     public static string Formatted<T1, T2>( this string format, T1 value1, T2 value2 )
  40:         where T1 : struct
  41:         where T2 : struct {
  42:         return string.Format( format, value1.ToSafeString(), value2.ToSafeString() );
  43:     }
  44:  
  45:     /// <summary>
  46:     /// Formats a string using the given format and the given values
  47:     /// </summary>
  48:     /// <typeparam name="T1">The type of the first argument.</typeparam>
  49:     /// <typeparam name="T2">The type of the second argument.</typeparam>
  50:     /// <typeparam name="T3">The type of the third argument.</typeparam>
  51:     /// <param name="format">The format.</param>
  52:     /// <param name="value1">The first value.</param>
  53:     /// <param name="value2">The second value.</param>
  54:     /// <param name="value3">The third value.</param>
  55:     /// <returns>The formatted string</returns>
  56:     public static string Formatted<T1, T2, T3>( this string format, T1 value1, T2 value2, T3 value3 )
  57:         where T1 : struct
  58:         where T2 : struct
  59:         where T3 : struct {
  60:         return string.Format( format, value1.ToSafeString(), value2.ToSafeString(), value3.ToSafeString() );
  61:     }
  62:  
  63:     /// <summary>
  64:     /// Formats a string using the given format and the given values.
  65:     /// </summary>
  66:     /// <param name="format">The format.</param>
  67:     /// <param name="values">The valuess.</param>
  68:     /// <returns>The formatted string</returns>
  69:     public static string Formatted( this string format, object[] values ) {
  70:         return string.Format( format, values );
  71:     }
  72: }

Als Anwendungsbeispiele einige Unit-Tests:

   1: [TestFixture]
   2: public class StringExtensionsTests {
   3:     [Test]
   4:     public void CanFormat() {
   5:         var format = "{0} + {1}";
   6:         var result = format.Formatted( 3, 4 );
   7:         var oldVersion = string.Format( format, 3, 4 );
   8:         Assert.AreEqual( "3 + 4", result );
   9:         Assert.AreEqual( oldVersion, result );
  10:     }
  11:  
  12:     [Test]
  13:     public void ToSafeStringHandlesNull() {
  14:         object x = null;
  15:         Assert.AreEqual( string.Empty, x.ToSafeString() );
  16:     }
  17:  
  18:     [Test]
  19:     public void ToSafeStringHandlesValueType() {
  20:         Assert.AreEqual( "5", 5.ToSafeString() );
  21:     }
  22:  
  23:     [Test]
  24:     public void ToSafeStringHandlesRefType() {
  25:         Assert.AreEqual( "foobar", "foobar".ToSafeString() );
  26:     }
  27: }

Zum einen ist der alternative Syntax kürzer. Es mag hier noch Geschmackssache sein, welche Variante (statische Format-Methode ggü. Extension Method). Die Variante mit den Extension Methods ist allerdings auch ein wenig effizienter. Da die Formatted mit einem, zwei oder drei Parametern generisch sind, findet kein Boxing bei der Übergabe der Argumente statt.

HTML Strings

Bei der Ausgabe von Strings in einem HTML Dokument sollte der Text allein schon aus Sicherheitsgründen HTML-codiert werden, besonders, wenn es sich dabei um Benutzereingaben handelt. Dazu steht die Methode HtmlEncode von HttpUtility bzw. HttpServerUtility bereit. Da ich es lästig finde, stets Server.HtmlEncode(derText) bzw.gar HttpUtility.HtmlEncode(derText) zu verwenden, habe ich eine Klasse geschrieben, die HtmlEncode als Extension-Methode der String-Klasse hinzufügt. Das ganze habe ich noch erweitert mit

  • einem Pendant für UrlEncode
  • den Gegenstücken HtmlDecode und UrlDecode
  • der Methode ToHtmlString, einer Extension für Object, die die Ausgabe von ToString() Html-kodiert (bzw. String.Empty zurückgibt bei einer verwendung mit null)
  • dem ToHtmlString-Pendant für URLs: ToUrlString

Der Code:

   1: /// <summary>
   2: /// Extension methods for <see cref="string"/> and <see cref="object"/> that are useful in the context
   3: /// of web applications.
   4: /// </summary>
   5: public static class WebStringExtensions {
   6:     /// <summary>
   7:     /// HTML encodes the given string.
   8:     /// </summary>
   9:     /// <param name="text">The text.</param>
  10:     /// <returns>The HTML encoded version of <paramref name="text"/></returns>
  11:     public static string HtmlEncode( this string text ) {
  12:         return HttpUtility.HtmlEncode( text );
  13:     }
  14:  
  15:     /// <summary>
  16:     /// Decodes the given HTML encoded string.
  17:     /// </summary>
  18:     /// <param name="text">The text.</param>
  19:     /// <returns>The plain version of the HTML encoded <paramref name="text"/></returns>
  20:     public static string HtmlDecode( this string text ) {
  21:         return HttpUtility.HtmlDecode( text );
  22:     }
  23:  
  24:     /// <summary>
  25:     /// Calls <see cref="object.ToString()"/> on the given object and HTML encodes the result.
  26:     /// </summary>
  27:     /// <param name="item">The item.</param>
  28:     /// <returns>
  29:     /// The HTML encoded version of <paramref name="item"/> or <see cref="string.Empty"/> if
  30:     /// <paramref name="item"/> was null.
  31:     /// </returns>
  32:     public static string ToHtmlString<T>( this T item ) {
  33:         return item.ToSafeString().HtmlEncode();
  34:     }
  35:  
  36:     /// <summary>
  37:     /// Encodes a string to be used inside an url.
  38:     /// </summary>
  39:     /// <param name="text">The text.</param>
  40:     /// <returns>The URLs encoded version of <paramref name="text"/></returns>
  41:     public static string UrlEncode( this string text ) {
  42:         return HttpUtility.UrlEncode( text );
  43:     }
  44:  
  45:     /// <summary>
  46:     /// Decodes a string that was url encoded.
  47:     /// </summary>
  48:     /// <param name="text">The text.</param>
  49:     /// <returns>The plain version of the URL encoded <paramref name="text"/></returns>
  50:     public static string UrlDecode( this string text ) {
  51:         return HttpUtility.UrlDecode( text );
  52:     }
  53:  
  54:     /// <summary>
  55:     /// Calls <see cref="object.ToString()"/> on the given object and HTML encodes the result.
  56:     /// </summary>
  57:     /// <param name="item">The item.</param>
  58:     /// <returns>
  59:     /// The URL encoded version of <paramref name="item"/> or <see cref="string.Empty"/> if
  60:     /// <paramref name="item"/> was null.
  61:     /// </returns>
  62:     public static string ToUrlString<T>( this T item ) {
  63:         return item.ToSafeString().UrlEncode();
  64:     }
  65: }

Hier einige Unit-Tests die direkt die Verwendung der Methoden zeigen:

   1: [TestFixture]
   2: public class WebStringExtensionsTests {
   3:     [Test]
   4:     public void CanHtmlEncode() {
   5:         var original = "für";
   6:         var encoded = original.HtmlEncode();
   7:         var expectedEncoded = "f&#252;r";
   8:         Assert.AreEqual( expectedEncoded, encoded );
   9:     }
  10:  
  11:     [Test]
  12:     public void CanHtmldecode() {
  13:         var original = "f&#252;r";
  14:         var decoded = original.HtmlDecode();
  15:         var expectedDecoded = "für";
  16:         Assert.AreEqual( expectedDecoded, decoded );
  17:     }
  18:  
  19:     [Test]
  20:     public void CanUrlEncode() {
  21:         var original = "für";
  22:         var encoded = original.UrlEncode();
  23:         var expectedEncoded = "f%c3%bcr";
  24:         Assert.AreEqual( expectedEncoded, encoded );
  25:     }
  26:  
  27:     [Test]
  28:     public void CanUrlDecode() {
  29:         var original = "f%c3%bcr";
  30:         var decoded = original.UrlDecode();
  31:         var expectedDecoded = "für";
  32:         Assert.AreEqual( expectedDecoded, decoded );
  33:     }
  34:  
  35:     [Test]
  36:     public void CanUseToHtmlString() {
  37:         var text = new MyCustomObject( 1, 2 ).ToHtmlString();
  38:         Assert.AreEqual( "a &lt;= b", text );
  39:         text = new MyCustomObject( 2, 1 ).ToHtmlString();
  40:         Assert.AreEqual( "a &gt; b", text );
  41:     }
  42:  
  43:     [Test]
  44:     public void CanHandleNullInToHtmlString() {
  45:         object o = null;
  46:         var text = o.ToHtmlString();
  47:         Assert.AreEqual( string.Empty, text );
  48:     }
  49:  
  50:     [Test]
  51:     public void CanUseToUrlString() {
  52:         var text = new MyCustomObject( 1, 2 ).ToUrlString();
  53:         Assert.AreEqual( "a+%3c%3d+b", text );
  54:         text = new MyCustomObject( 2, 1 ).ToUrlString();
  55:         Assert.AreEqual( "a+%3e+b", text );
  56:     }
  57:  
  58:     [Test]
  59:     public void CanHandleNullInToToUrlString() {
  60:         object o = null;
  61:         var text = o.ToUrlString();
  62:         Assert.AreEqual( string.Empty, text );
  63:     }
  64:  
  65:     #region Nested type: MyCustomObject
  66:     private class MyCustomObject {
  67:         private readonly int a;
  68:         private readonly int b;
  69:  
  70:         public MyCustomObject( int a, int b ) {
  71:             this.a = a;
  72:             this.b = b;
  73:         }
  74:  
  75:         public override string ToString() {
  76:             return a > b ? "a > b" : "a <= b";
  77:         }
  78:     }
  79:     #endregion
  80: }

Auch hier arbeiten wir wieder weitestgehend Resourcen schonend. Gerade ToHtmlString und ToUrlString sind im Zuge von Databinding extrem nützlich:

   1: <asp:Label runat="server" ID="label" Text='<%# Eval("UserName").ToHtmlString() %>' />
   2: <asp:HyperLink runat="server" ID="link" NavigateUrl='<%# string.Format("~/Profile.aspx?user={0}", Eval("LoginName").ToUrlString()) %>' />

Posted in: Snippets

Tags: