Visto che abbiamo inziato a ballare… balliamo :-D
Gli anonymous methods e ancor di più le lambda expressions introducono la possibilità di “scroccare” al proprio codice un’espressvità eccelsa, imho… ;-)
class Program
{
static void Main( string[] args )
{
var worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler( OnWorkerDoWork );
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler( OnWorkerRunWorkerCompleted );
worker.RunWorkerAsync();
}
static void OnWorkerDoWork( object sender, DoWorkEventArgs e )
{
//Code...
}
static void OnWorkerRunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
{
//Code
}
}
Questo esempio non ha assolutamente nulla di sbagliato, ma questo:
class Program
{
static void Main( string[] args )
{
var worker = new BackgroundWorker();
worker.DoWork += ( s, e ) => { /* code */ };
worker.RunWorkerCompleted += ( s, e ) => { /* code */ };
worker.RunWorkerAsync();
}
}
forse, questione di gusti sia chiaro, in termini di leggibilità vince. Possiamo però andare oltre e “produrre”, sempliemente wrappando il BackgroundWorker e non reinventando la ruota, questo:
class Program
{
static void Main( string[] args )
{
AsyncWorker.Create<TArgument, TResult>()
.BeforeExecution( arg => { /* code */ } )
.OnExecution( result => { /* code */ } )
.OnAsyncError( err => { /* code */ } )
.Run( argument… );
}
}
rendendo, sempre imho, ancora più bello il risultato. Nello specifico l’AsyncWorker è nato per l’uso in un’applicazione WPF e quindi implementa di suo il dispatch degli eventi nel thread corretto sempre in maniera fluente:
AsyncWorker.Create<String, IEnumerable<IMyEntity>>()
.DispatchUsing( dispatcher )
.NotifyUsing( broker )
....
Spinto dalla stessa necessità di alta leggibilità, di standardizzazione, di semplicità e rapidità di scrittura, in attesa del framework 4.0 (ndr CodeContracts dato che PostSharp funziona a singhiozzo…) mi sono scritto una semplicissima libreria per fare la validazione dei parametri in ingresso ad un metodo:
public static class Ensure
{
public static Ensure<T> That<T>( T obj )
{
return new Ensure<T>( obj );
}
}
la classe static ensure offre un entry point statico per supportare le fluent interface, grazie alla type inference è possibile scrivere semplicemente:
public void MyMethod( String argument )
{
var ensure = Ensure.That( argument );
}
e avere in mano un Ensure<String>. La classe generica Ensure<T>, che ha comunque un costruttore pubblico per consetire l’uso tradizionale, ha alcuni metodi utili per la validazione in maniera fluente:
public void MyMethod( String argument )
{
Ensure.That( argument ).IsNotNull();
}
che è implementato così:
public Ensure<T> IsNotNull()
{
this.If( s => s == null )
.Then( ( obj ) =>
{
throw new ArgumentNullException( this.Name );
} );
return this;
}
Il metodo Named( String ) serve per assegnare un nome al parametro che si sta validando, in modo che le eventuali exception abbiano il “parameterName” correttamente compilato.
public Ensure<T> Named( String parameterName )
{
this.Name = parameterName;
return this;
}
Una prima implementazione prevedeva un uso, decisamente sagace, delle lambda expression per evitare quella che a tutti gli effetti è una bruttura e consentire di scrivere, grazie a questo cotruttore:
public Ensure( Expression<Func<T>> obj )
{
if( obj == null )
{
throw new ArgumentNullException( "obj", "Cannot use a null Expression<Func<T>> as Ensure ctor parameter." );
}
Func<T> func = obj.Compile();
this.InspectedObject = func();
var expression = obj.Body as MemberExpression;
var member = expression.Member as FieldInfo;
this.Name = member.Name;
}
e questo entry point:
public static Ensure<T> That<T>( Expression<Func<T>> obj )
{
return new Ensure<T>( obj );
}
questa cosa secondo me bellissima:
public void MyMethod( String argument )
{
Ensure.That( ()=> argument ).IsNotNull();
}
peccato che tutto ciò abbia delle performance che non rasentano neanche lontanamenete l’accettabile… veramente un peccato.
Tornando all’implementazione atttuale quello che abbiamo visto lascia intendere che ci sia un metodo If(), un metodo Then() e anche, perchè no, un metodo Else() che accettano dei delegati (Predicate<T>/Action<T>):
Boolean state = false;
public Ensure<T> If( Predicate<T> predicate )
{
state = predicate( this.InspectedObject );
return this;
}
public Ensure<T> Then( Action<T> action )
{
if( state )
{
action( this.InspectedObject );
}
return this;
}
public Ensure<T> Else( Action<T> action )
{
if( !state )
{
action( this.InspectedObject );
}
return this;
}
public Ensure<T> Then( Action<T, String> action )
{
if( state )
{
action( this.InspectedObject, this.Name );
}
return this;
}
public Ensure<T> Else( Action<T, String> action )
{
if( !state )
{
action( this.InspectedObject, this.Name );
}
return this;
}
Ci sono poi una serie di extension methods per aggiungere funzionalità per tipi specifici di Ensure<T>, ad esempio:
public static class StringEnsureExtension
{
public static Ensure<String> IsNotEmpty( this Ensure<String> validator )
{
validator.If( s => s.Length == 0 )
.Then( ( obj ) =>
{
throw new ArgumentOutOfRangeException( validator.Name );
} );
return validator;
}
}
che fa si che se il parametro validato è di tipo System.String possiamo scrivere:
public void MyMethod( String argument )
{
Ensure.That( argument )
.Named( "argument" )
.IsNotNull()
.IsNotEmpty();
}
che è più leggibile/manutenibile di:
public void MyMethod( String argument )
{
if( argument == null )
{
throw new ArgumentNullException( "argument" );
}
if( argument.Length == 0 )
{
throw new ArgumentOutOfRangeException( "argument" );
}
}
Buona “fine” domenica a tutti ;-)
.m