Sunday, January 30, 2011

The Dynamic Sugar Library

The Dynamic Sugar Library provides methods, functions and classes inspired by the dynamic language Python and JavaScript to write more readable code in C#.

It's all about Syntactic Sugar. Obviously this is based on my own taste.

Is it syntactic saccharin? You decide!

  • Some extensions method to the class List<T> are borrowed from the javascript library underscore.js.




But Why?

To make C# code shorter, simpler and cleaner.

Let's look at a first example - Formatting string

Let's format a string
// Regular syntax
var s = string.Format("[{0}] Age={1:000}", "TORRES", 45);

// Dynamic Sugar syntax
var s = "[{LastName}] Age={Age:000}".Format( new { LastName="TORRES", Age=45 } );

//
The new Format() method support the following as parameter any POCO object, Anonymous Type, IDictionary<K,V>, ExpandoObject and DynamicSugar.DynamicBag.

Let's look at a second example - How to instanciate, populate and use a List<T>

Let's check if one value exist in a list.
// Regular syntax
List<int> someIntegers = new List<int>() { 1, 2, 3 };
int i                  = 2;
if(someIntegers.Contains(i)){

    Console.WriteLine("2 is in the list");
}
Here is the syntax with DynamicSugar
int i = 2;
if(i.In(DS.List( 1, 2, 3 ))){

    Console.WriteLine("2 is in the list");
}

Let's look at a third example - How to instanciate, populate and pass a dictionary to a method

void InitializeSettings(IDictionary<string, object> settings){
            
}

// Regular syntax
InitializeSettings( new Dictionary<string, object>() {
    { "UserName", "RRabbit"  },
    { "Domain"  , "ToonTown" },
    { "UserID"  , 234873     }
});

// Dynamic Sugar syntax
InitializeSettings( DS.Dictionary( new {
    UserName = "RRabbit" ,
    Domain   = "ToonTown",
    UserID   = 234873
}));

Let's look at a forth example - Function returning multiple values

A C# function can only return one value, but with Dynamic Sugar we can change that.
// Here is a function returning 3 values using Dynamic Sugar
static dynamic ComputeValues() {
                        
    return DS.Values( new { a=1, b=2.0, c="Hello" } );
}

// Here is how to use the results
static void MultiValuesSample() {
                        
    var values = ComputeValues();
    Console.Write(values.a);
    Console.Write(values.b);
    Console.Write(values.c);
}



Extension Methods For Classes


Format() Method

Extend any class with the method Format(), and start formating string using the properties name of the class. Post fix the name with ":format" and format the value of the property.
The method knows how to format the following types: List<T>, array, Dictionary<K,V> and user custom types.
You can also call member function with no input parameters.
The method fall back on the String.Format(), so you can also combine "{0}" in the format.
Person p = new Person() {

    LastName        = "TORRES"  ,
    FirstName       = "Frederic",
    BirthDay        = new DateTime(1964,12, 11),
    DrivingLicenses = DS.List("Car", "Moto Bike")
};
Console.WriteLine( // Call 4 properties in the format
    p.Format("FullName:'{LastName},{FirstName}', BirthDay:{BirthDay:MM/dd/yyyy}, DrivingLicenses:{DrivingLicenses}")
);
Console.WriteLine( // Call a method in the format
    p.Format("LoadInformation:{LoadInformation()} ")
);

Output:
FullName:'TORRES,Frederic', BirthDay:12/11/1964, DrivingLicenses:["Car", "Moto Bike"]
LoadInformation:Information

Static Members


Range()

Instead of writing this
for (int i = 0; i < 5; i++) {
                
    Console.WriteLine(i);
}
Write that
foreach(var i in DS.Range(5)){

    Console.WriteLine(i);
}

// Definition
public static List<int> Range(int max);
public static List<int> Range(int max, int increment);
public static List<int> Range(int start, int max, int increment);



List() Method

To quickly create a List<T>.
var l1 = DS.List(1, 2, 3, 4);
var l2 = DS.List("A", "B", "C");


Dictionary() Method

To quickly create a IDictionary<String, Object>.
var d1 = DS.Dictionary( LastName:"Pascal", FirstName:"Blaise" );

Extension Methods For All Types


In() Method

The in operator in Python, is translated with an extension method named In() available to all the types in the .NET framework. It is may be a little bit pushy.
You must implement the method
public override bool Equals(object obj);
for your custom classes to be able to use the function In().

Here is what you can write :
int i = 1;

if(i.In(1,2,3)){

}

List<int> l = DS.List(1,2,3);

if(i.In(l)){

}

var state = "good";

if(state.In("good","bad","soso")){

}

enum CustomerType {

  Good,
  Bad,
  SoSo
}

var customerType = CustomerType.Good;

if(customerType.In(CustomerType.Good, CustomerType.SoSo)){

}

Dictionary() Method

The Dictionary() is an extension method that can be applied to any POCO.
This is the equivalent of the Python __dict__ member.
// Extension method for the class Person
public static IDictionary<string,object> Dictionary(this Person person, List<string> propertiesToInclude = null) {
    return DynamicSugar.ReflectionHelper.GetDictionary(person, propertiesToInclude);
}
// Usage
Person p = new Person() {

  LastName  = "TORRES",
  FirstName = "Frederic",
  BirthDay  = new DateTime(1964, 12, 11)
};
foreach(var k in p.Dictionary()){

  Console.WriteLine("{0}='{1}'", k.Key, k.Value);
}

Extension Methods For List<T>

All methods return a brand new List<T>.


Map() and Format() Method

The method Map() allow to process each element of a list and return a new list of the same type.
The method Format() for a List<T> allow to format a list into a string.
// Compute the square values of a list

var l = DS.List(1,2,3,4).Map( e => e*e );
Console.WriteLine(l.Format()); // => 1, 4, 9, 16

var l2 = DS.Map( DS.Range(10), e => e*e );
Console.WriteLine(l2.Format("'{0}'", ",")); // => '0','1','4','9','16','25','36','49','64','81'

//

Inject() Method

Inject() inspired by Ruby. The function re-uses the .NET Aggregate() function.
Yes, it is not exactly the same, as the end result type must be the same as the type of the list.

// Sum the values from 0 to 4

var l = DS.Range(5).Inject( (v,e) => v += e ) ;
var l = DS.List(0,1,2,3,4).Inject( (v,e) => v += e ) ;

Filter() Method

Filter is the same as FindAll(), I just prefer Filter().
var l = DS.Range(10).Filter(e => e % 2 == 0);
Console.WriteLine( l.Format() );

Map() Method

This map function can only return a list of same type, after all C# is not Python.
string MyConst = "Hi ";
var l          = DS.List("fred", "joe", "diane").Map(e => MyConst + e));
// => ["Hi fred", "Hi joe", "Hi diane"]

ToFile() and FromFile() Methods

To write and read List<T> from a text file where T is a value type.
var l1        = DS.Range(10);
var fileName  = @"{0}\DSSharpLibrary_UnitTests.txt".format(Environment.GetEnvironmentVariable("TEMP"));

l1.ToFile(fileName, true);

var l2        = DS.FromFile<int>(fileName);

DS.AssertListEqual(l1, l2);            

Include() Method

var l1 = DS.Range(10);

if(l1.Include(5)){

}
if(l1.Include(1, 2, 3)){

}
if(l1.Include(DS.List(1, 2 , 3))){

}  

Without() Method

Borrowed from underscore.js.
var l1 = DS.Range(10);            
var l2 = l1.Without(0, 2, 4, 6, 8);
var l3 = l1.Without(DS.List(0, 2, 4, 6, 8));
Console.WriteLine(l2.Format()); // => 1, 3, 5, 7, 9
Console.WriteLine(l3.Format()); // => 1, 3, 5, 7, 9

First(), Last(), Rest(), IsEmpty() Methods

Borrowed from underscore.js.
var l1 = DS.Range(10);            

while(!l1.IsEmpty()){

    var first = l1.First();
    var last  = l1.Last();
    l1        = l1.Rest();
}

Identical() Methods

var l1 = DS.Range(10);            
var l2 = DS.Range(10);            

if(l1.Identical(l2)){

}

Pluck() Methods

Borrowed from underscore.js.
var people = DS.List(
    new Person() { LastName = "Descartes",   FirstName = "Rene",          BirthDay = new DateTime(1596,3,31) },
    new Person() { LastName = "Montesquieu", FirstName = "Charles-Louis", BirthDay = new DateTime(1689,1,18) },
    new Person() { LastName = "Rousseau",    FirstName = "JJ",            BirthDay = new DateTime(1712,3,31) }
);
Console.WriteLine(people.Pluck<int, Person>("Age").Format()); // => 415, 322, 299 in 2010          

//

Reject() Methods

Borrowed from underscore.js.
// Remove some elements based on an lambda expression
Console.WriteLine(
            DS.Range(10).Reject(e => e % 2 == 0).Format()
        ); // => 1, 3, 5, 7, 9



Class RazorHelper - An easy way to use the Microsoft Razor Template Engine outside of the MVC framework

The Razor helper class does the following:
  • Simplify the execution of Razor templates
  • Compiled and cache templates in memory
  • Implement the concept of bag populated from any .NET POCO, Anonymous Type or ExpandoObject to pass variables to be rendered in the template.
See unit tests from usage samples
string BAG_TEMPLATE = @"
var @bag.Class = {
    ID          : @bag.ID,
    LastName    :'@bag.LastName',
    FirstName   :'@bag.FirstName',
    
    Execute: function(i){
        
    }
}
";
dynamic bag   = new ExpandoObject();
bag.LastName  = "DESCARTES";
bag.FirstName = "Rene";
bag.Class     = "User";
bag.ID        = 1234;

using (var r = new RazorHelper()) {

    var t = r.Run("JavascriptTemplate", BAG_TEMPLATE, bag);
}

No comments:

Post a Comment