Signum Framework Logo
"Open framework that encourages convention over configuration, using C# code,
not XML files, to model at the right level of abstraction and achieve deadlines.
...but also has a full Linq provider, and syncs the schema for you!"
Login
RSS

Search

»



Main
Index
Map
Videos
Download
Source code
Tutorials
Forum
FAQ



Image



PoweredBy


Signum.Utilities.Extensions

As a general rule we tried to avoid adding ExtensionMethods to basic types, like object (or a free generic type) to avoid cluttering IntelliSense.

The class here, however, contains functionality so widely used in our code that it's worth giving them license to clutter.

Image

Simple Methods

Parse Number

Contains extension methods to make TryParse more user friendly by using Nullables:

public static int? ToInt(this string str) 
public static long? ToLong(this string str)
public static short? ToShort(this string str)
public static float? ToFloat(this string str)
public static double? ToDouble(this string str)
public static decimal? ToDecimal(this string str)

And the equivalent overloads that throw a new FormatException with your own custom error message if the parsing fails:

public static int ToInt(this string str, string error)
public static long ToLong(this string str, string error)
public static short ToShort(this string str, string error)
public static float ToFloat(this string str, string error)
public static double ToDouble(this string str, string error)
public static decimal ToDecimal(this string str, string error)

Map

Sometimes you need to store an expression result in a variable to use more than once in an expression:

FileInfo fi = new FileInfo("MyPicture.bmp"); 

Console.WriteLine("Name {0} Size {1}".Formato(fi.Name, fi.Length));


Map method allows you to avoid 'braking the line' doing this:

Console.WriteLine(new FileInfo("MyPicture.bmp").Map(fi=>"Name {0} Size {1}".Formato(fi.Name, fi.Length))); 

Map is deffined just like this.

public static R Map<T, R>(this T t, Func<T, R> func)
{
    return func(t);
}


You can think of map as a Try (see below) that doesn't handle nullability, or as a Select for single elements.

Try

'Try' methods are the ultimate 'break line removers'. Imagine you have a pseudo-code like this:

var a = Foo.Bar.Baz; 

And then someone told you that Foo or Bar can be null values, then you have to change the code to:

//Imperative, and access Foo 3 times and Bar twice
var a = null; 
if(Foo != null) 
  if(Foo.Bar != null)
      a = Foo.Bar.Baz; 

//Functional, still access Foo 3 times and Bar twice var a = Foo != null? (Foo.Bar != null? Foo.Bar.Baz : null) : null;

//Imperative, and loong var a = null; var f = Foo; if(f != null) { var b = f.Bar; if(b != null) a = b.Baz; }

Since we don't have a ?. operator in .Net, our solution is to create the Try methods, that deal and propagate nullability the right way by using lambdas. This is the idea:

var a = Foo.Try(f=>f.Bar).Try(b=>b.Baz); 

public static R Try<T, R>(this T t, Func<T, R> func) { if (t == null) return null; return func(t); }

If Foo is null, then the first Try returns null without evaluating the lambda and the second one does the same. If Foo is not null, the first Try is evaluated, let's say that then Bar is null, then the second Try returns null without evaluating the second lambda. If neither Foo or Bar are null, the the final Baz value is returned.

The trick here is that we are using Extension Methods, and since they are really static methods you can call them even when Foo is null without a NullReferenceException being thrown.

Unfortunately, nullables makes things a bit harder and the method above won't work on any case. Instead, different methods are needed depending on the called object and the lambda result being value types, nullables, or reference types because C# overloading resolution doesn't play well with generic constraints:

public static R TryCC<T, R>(this T t, Func<T, R> func) where T : class where R : class

public static R? TryCS<T, R>(this T t, Func<T, R> func) where T : class where R : struct public static R? TryCS<T, R>(this T t, Func<T, R?> func) where T : class where R : struct

public static R TrySC<T, R>(this T? t, Func<T, R> func) where T : struct where R : class

public static R? TrySS<T, R>(this T? t, Func<T, R> func) where T : struct where R : struct public static R? TrySS<T, R>(this T? t, Func<T, R?> func) where T : struct where R : struct

As you see (if you look carefully), the only differentiation needed is between value and reference types, overloading resolution can cope with nullable return types in the lambda, so you just have to remember:

Use TryC_ over classes, use TryS_ over nullable types. Use Try_C when returning classes, use Try_S when returning value types or nullables.

Let's see a simple example:

string str = DateTime.Today.DayOfWeek == DayOfWeek.Monday? "hello" : 
                                          DayOfWeek.Sunday? "":
                                          : null  

int? stringLenght = str.TryCS(s=>s.Length);

This code uses TryCS because str is a string (class) while Length returns an int (string). It works on Mondays (returning 4), on Sundays (returning 0) and on any other day (returning null).

Let's see a more complex example:

string departamentBoshNameLengthX2 = 
             Departament
            .TryCC(d=>d.Boss)
            .TryCC(b=>b.Name)
            .TryCS(n=>n.Length)
            .TrySS(len=>len * 2)
            .TrySC(len2=>len2.ToString()); 

Look at how gracefully nullability propagates across the expression. It's a pity to have to think about CC/CS/SC/SS all the time, but I think it's worth it anyway (just imagine how long it would be using code as in the first example).

TryToString Image

A shortcut for using ToString with Try behavior.

public static string TryToString(this object obj)
public static string TryToString(this object obj, string defaultValue)

Example:

((int?)null).TryToString(); //Returns null instead of throwing exception
((int?)null).TryToString("-No Value-"); //Returns -No Value-

Do

Sometimes you need to store an expression result in a variable to make many actions over it:

Button b = new Button { Text = "Ok" };

Grid.SetColumn(b, 3);

grid.AddChild(b);

'Do' extension method allows you to avoid 'braking the line' doing this:

grid.AddChild(new Button { Text = "Ok" }.Do( b => Grid.SetColumn(b,3) ); 

'Do' is defined like this.

public static T Do<T>(this T t, Action<T> action)
{
   action(t);
   return t;
}

As yo see, it returns the initial object to allow chainability.

'Do' is useful when you need to initialize an object but you need to call methods or attach events as well.

return new Button 
       { 
         Text = "Ok" 
       }
       .Do( b => Grid.SetColumn(b,3) )
       .Do( b =>  b += Mouse_Click  ); 

TryDo

Closing the circle, TryDo allows 'Do' behaviour over objects that could be null.

Map -> Try
Do -> TryDo

There are two overloads:

public static T TryDoC<T>(this T t, Action<T> action) where T : class
{
    if (t != null)
        action(t);
    return t;
}

public static T? TryDoS<T>(this T? t, Action<T> action) where T : struct { if (t != null) action(t.Value); return t; }

Example:

Departament.TryCC(d=>d.Boss).TryDo(b=>b.Fire()); 

ThrowIfNull

Sometimes you want to be sure that an object is not null before using it to throw a special message. ThrowIfNull allows to do that without braking the line.

if(Departament.Boss == null)
   throw new NullReferenceException("A Boss is mandatory to Promote");

Departament.Boss.Promote();

With ThrowIfNull you can assert not nullability on the fly:

Departament.Boss.ThrowIfNullC("A Boss is mandatory to Promote").Promote(); 

This specially useful on queries, where you will have to change a lambda to statement body to do something like this.

As usual, there are two overloads:

  public static T TryDoC<T>(this T t, Action<T> action) where T : class
  {
      if (t != null)
        action(t);
      return t;
  }

public static T? TryDoS<T>(this T? t, Action<T> action) where T : struct { if (t != null) action(t.Value); return t; }

DefaultToNull

Transforms value type's default value to null:

public static T? DefaultToNull<T>(this T value)
            where T : struct
{
    return EqualityComparer<T>.Default.Equals(default(T), value) ? (T?)null : value;
}

Example:

DateTime.MinValue.DefaultToNull() == null  // Is True

NotFoundToNull Image

It's common to return -1 to mean 'index not found'. Unfortunately the convenient coalesce operator doesn't work with -1, but with (int?)null.

This simple method converts -1 to null.

public static int? NotFoundToNull(this int value)
{
    return value == -1 ? null : (int?)value; 
}

list.SelecteIndex = countries.FindIndex(a=>a == User.Country).NotFoundToNull() ?? countries.IndexOf(Spain);

Collection Methods

For

'For' is the functional equivalent of the for statement.

  public static IEnumerable<T> For<T>(this T start, Func<T, bool> condition, Func<T, T> incremento)
  {
      for (T i = start; condition(i); i = incremento(i))
         yield return i;
  }

Example:

1.For(i => i <= 1024, i => i * 2).ToString(","));

// Result: 1,2,4,8,16,32,64,128,256,512,1024

'For' is also the generalization of the methods above that make simple cases simpler to write.

To

To allows a Rubish style of generating sequences of numbers to, presumably, start a query on it.

  public static IEnumerable<int> To(this int start, int endNotIncluded)
  {
     for (int i = start; i < endNotIncluded; i++)
        yield return i;
  }

It has two main differences over Enumerable.Range:
  • It takes the end and not the count as second parameter.
  • It's way shorter.

Enumerable.Range(5,5);
//Result: 56789

Could be written now:

5.To(10); 
//Result: 56789

'To' uses C language convention of not reaching the end value in a for.

There's also another overload that takes a step parameter:

0.To(10, 2); 
//Result: 02468

DownTo

Similar to 'To' but decreasing instead of increasing.

public static IEnumerable<int> DownTo(this int startNotIncluded, int end)
{
    for (int i = startNotIncluded - 1; i >= end; i--)
        yield return i;
}

As you see, it also uses C 'inverse for' convention, so the initial value is not included, while the final one is (we did the opposite with in 'To'). It satisfies the property:

A.To(B) == B.DownTo(A).Reverse()

Example:

10.DownTo(5).ToString(""); 

//Result: 98765

It also has an overload with step (positive!!): Example:

10.DownTo(0, 2).ToString(""); 

//Result: 86420

Folow

Finally, Follow is another concretion of For method. In this case it assumes that the sequence will end when the first null value is reached.

 public static IEnumerable<T> FollowC<T>(this T start, Func<T, T> next) where T : class
 {
     for (T i = start; i != null; i = next(i))
         yield return i;
 }

It's used to generate a sequence of elements by following a path from an initial and taking and action each time. Like following the Parent chain:

 FrameworkElement first = ...; 
 IEnumerable<FrameworkElement> parents = first.FollowC(fe => fe.Parent); 
 Grid grid = parens.OfType<Grid>().First(); 

Or the sorter version:

 Grid grid = first.FollowC(fe => fe.Parent).OfType<Grid>().First(); 
Creative Commons License Signum Framework Site by Signum Software is licensed under a Creative Commons Attribution 3.0 License.
Powered by ScrewTurn Wiki version 3.0.5.600.