Este artículo proviene de un motor de traducción automática.

Patrones en la práctica

Programación funcional para cada día .NET Development

Jeremy Miller

¿Qué es el más importante avance en el ecosistema .NET durante los últimos tres o cuatro años? Podría estar tentado nombre una nueva tecnología o el marco, como Windows Presentation Foundation (WPF) o Windows Communication Foundation (WCF). Para mí personalmente, sin embargo, diría que las adiciones eficaces a los lenguajes C# y Visual Basic a través de las dos últimas versiones de .NET Framework han tenido un impacto mucho más significativo en Mis actividades diarias de desarrollo. En este artículo me gustaría examinar en particular cómo la nueva compatibilidad con las técnicas de programación funcionales en .NET 3.5 puede ayudarle a hacer lo siguiente:

  1. Hacer el código más declarativo.
  2. Reducir los errores en código.
  3. Escribir menos líneas de código para muchas tareas comunes.

La característica de LINQ (Language Integrated Query) de todos sus muchas versiones es un uso obvio y eficaz de programación funcional en. NET, pero sólo la punta del iceberg.

Mantener con el tema de "desarrollo diaria"Ha basado la mayoría de los ejemplos de código en C# 3.0 con algunos JavaScript sprinkled en. Tenga en cuenta que algunos de los lenguajes de programación más recientes, otros para CLR, como IronPython, IronRuby y F #, tienen compatibilidad sustancialmente más segura o sintaxis más ventajoso para las técnicas de programación funcionales que se muestra en este artículo. Desgraciadamente, la versión actual de Visual Basic no admite funciones lambda multilínea, por lo tanto, muchas de las técnicas que se muestra aquí no son como utilizable en Visual Basic. Sin embargo, podrían recomendamos a tener en cuenta estas técnicas en preparación para la próxima versión de idioma, los desarrolladores de Visual Basic en Visual Studio 2010 de envío.

Funciones first-Class

Algunos elementos de programación funcional son posibles en C# o Visual Basic porque tenemos ahora funciones de primera clase que pueden pasarse alrededor entre métodos, mantenidos como variables o incluso devuelto desde otro método. Los delegados anónimos de .NET 2.0 y la expresión lambda más reciente de .NET 3.5 son cómo C# y Visual Basic implementan funciones de primera clase, pero "Expresión lambda"significa algo más específico en informática. Otro término común para una función de primera clase es "bloquear". Para el resto de este artículo utilizaré el término "bloquear"para denotar funciones de primera clase, en lugar de "cierre"(un tipo específico de primera clase de función explica a continuación) o "Lambda"Para evitar las imprecisiones accidentales (y wrath de expertos de programación funcionales real). Un cierre contiene las variables definidas de fuera de la función de cierre. Si ha utilizado la biblioteca jQuery cada vez más popular para el desarrollo de JavaScript, probablemente usó cierres con bastante frecuencia. Aquí es un ejemplo del uso de un cierre, tomado de mi proyecto actual:

// Expand/contract body functionality          
var expandLink = $(".expandLink", itemElement);
var hideLink = $(".hideLink", itemElement);
var body = $(".expandBody", itemElement);
body.hide();

// The click handler for expandLink will use the
// body, expandLink, and hideLink variables even
// though the declaring function will have long
// since gone out of scope.
expandLink.click(function() {
    body.toggle();
    expandLink.toggle();
    hideLink.toggle();
});

Este código se utiliza para configurar un efecto accordion bastante habitual para mostrar u ocultar contenido en nuestras páginas Web haciendo clic en un < un >elemento. Definimos el controlador de clic de la expandLink pasando una función de cierre que utiliza las variables creadas fuera el cierre. La función que contiene las variables y el controlador de clic se cerrará largo antes de que el expandLink hizo clic el usuario, pero el controlador de clic podrán utilizar las variables cuerpo y hideLink.

Lambdas como datos

En algunas circunstancias, puede utilizar la sintaxis de lambda para indicar una expresión en código que puede ser usado como datos lugar de ejecutarse. Especialmente no entiendo que la primera de instrucción que varias veces se leer, por lo tanto, veamos un ejemplo de tratar una lambda como datos de una asignación explícita objeto/relacional mediante la biblioteca Fluent NHibernate:

 

public class AddressMap : DomainMap<Address>
    {
        public AddressMap()
        {
            Map(a => a.Address1);
            Map(a => a.Address2);
            Map(a => a.AddressType);
            Map(a => a.City);
            Map(a => a.TimeZone);
            Map(a => a.StateOrProvince);
            Map(a => a.Country);
            Map(a => a.PostalCode);
        }
    }

Habla NHibernate nunca evalúa la expresión a = >a.Address1. En su lugar, analiza la expresión para buscar el nombre Dirección1 para utilizar en la asignación de NHibernate subyacente. Esta técnica ha propagado ampliamente a través de muchos proyectos de código abierto recientes en el espacio. NET. Utilizar expresiones lambda sólo para obtener objetos PropertyInfo y nombres de propiedad con frecuencia se denomina reflexión estática.

Pasar bloques

Uno de los motivos mejores para estudiar la programación funcional es aprender cómo primera clase funciones le permiten reducir la duplicación en el código al proporcionar un mecanismo más minuciosa para la composición de la clase. Procederá con frecuencia a través de secuencias de código que son esencialmente ídénticas en su forma básica, excepto para un paso en algún lugar en mitad de la secuencia. Con la programación orientada a objetos, puede utilizar herencia con el modelo de método de plantilla para intentar eliminar la duplicación. Encuentro que pasar bloques que representa la variable paso en el medio a otro método que implementa la secuencia básica para eliminar esta duplicación de forma más clara y más.

Una de las mejores formas para facilitar una API utilizar y menos propensa a errores es reducir el código repetitivo. Por ejemplo, considere el caso común de una API diseñado para tener acceso a un servicio remoto o el recurso como un objeto ADO.NET IDbConnection o una escucha de socket requiere una conexión con estado o persistente. Normalmente debe "abrir"la conexión antes de utilizar el recurso. Estas conexiones con estado suelen ser caros o escasa en términos de recursos, por lo que a menudo es importante para "Cerrar"la conexión tan pronto como haya terminado para liberar el recurso para otros procesos o subprocesos.

En el código siguiente se muestra una interfaz para la puerta de enlace a una conexión con estado de algún tipo representativa:

 

public interface IConnectedResource
    {
        void Open();
        void Close();

        // Some methods that manipulate the connected resource
        BigDataOfSomeKind FetchData(RequestObject request);
        void Update(UpdateObject update);
    }

Cada vez único que otra clase utiliza esta interfaz IConnectedResource, el método Open tiene que llamarse antes mediante cualquier otro método y el método Close debe siempre llamarse posteriormente, como se muestra en figura 1.

En un artículo anterior expliqué la idea de esencia frente a ceremonia en nuestros diseños. (Vea msdn.microsoft.com/magazine/dd419655.aspx de). La "esencia"de la ConnectedSystemConsumer responsabilidad de la clase es simplemente utilizar el recurso conectado para actualizar alguna información. Desgraciadamente, la mayoría del código en ConnectedSystemConsumer se ocupa de "la ceremonia de"de conectar y desconectar de la interfaz de IConnectedResource y tratamiento de errores.

Figura 1 mediante IConnectedResource

 

public class ConnectedSystemConsumer
{
private readonly IConnectedResource _resource;
public ConnectedSystemConsumer(IConnectedResource resource)
{
_resource = resource;
}
public void ManipulateConnectedResource()
{
try
{
// First, you have to open a connection
_resource.Open();
// Now, manipulate the connected system
_resource.Update(buildUpdateMessage());
}
finally
{
_resource.Close();
}
}
}

Peor aún es el hecho de que el "try/abrir o hacer cosas y finalmente/cierre"Ceremonia de código tiene que estar duplicado para cada uso de la interfaz IConnectedResource. Como he tratado antes, una de las mejores formas para mejorar el diseño es marca fuera de duplicación donde creeps en el código. Probemos un enfoque diferente a la API IConnectedResource mediante un bloque o cierre. En primer lugar, voy a aplicar el principio de separación de interfaz (consulte objectmentor.com/resources/articles/isp.pdf para obtener más información) para extraer una interfaz estrictamente para invocar el recurso conectado sin los métodos para abrir o cerrar:

 

public interface IResourceInvocation
    {
        BigDataOfSomeKind FetchData(RequestObject request);
        void Update(UpdateObject update);
    }

A continuación, crea una interfaz segunda que se utiliza estrictamente para obtener acceso al recurso conectado representado por la interfaz IResourceInvocation:

 

public interface IResource
    {
        void Invoke(Action<IResourceInvocation> action);
    }

Ahora, vamos a volver a escribir la clase ConnectedSystemConsumer para utilizar la API más reciente, estilo funcional:

 

public class ConnectedSystemConsumer
    {
        private readonly IResource _resource;
 
        public ConnectedSystemConsumer(IResource resource)
        {
            _resource = resource;
        }

        public void ManipulateConnectedResource()
        {
            _resource.Invoke(x =>
            {
                x.Update(buildUpdateMessage());
            });
        }
    }

Esta nueva versión de ConnectedSystemConsumer ya no tiene cuidado acerca de cómo configurar o destruir los recursos conectados. En efecto, ConnectedSystemConsumer indica simplemente la interfaz IResource a "Ir a la primera IResourceInvocation vea y darle estas instrucciones"pasando un bloque o cierre al método IResource.Invoke. Repetitiva todo lo que "try/abrir o hacer cosas o finally/cerrar"Ceremonia de código que se queja antes está ahora en la implementación concreta de IResource, como se muestra en de figura 2.

Figura 2 implementación concreto de IResource

 

frepublic
class Resource : IResource
{
public void Invoke(Action<IResourceInvocation> action)
{
IResourceInvocation invocation = null;
try
{
invocation = open();
// Perform the requested action
action(invocation);
}
finally
{
close(invocation);
}
}
private void close(IResourceInvocation invocation)
{
// close and teardown the invocation object
}
private IResourceInvocation open()
{
// acquire or open the connection
}
}

Diría que hemos mejorado nuestro diseño y el uso de API colocando la responsabilidad de apertura y cierre la conexión con el recurso externo en la clase de recursos. Hemos mejorado también la estructura de nuestro código por encapsular los detalles de problemas de infraestructura desde el flujo de trabajo principal de la aplicación. La segunda versión de ConnectedSystemConsumer sabe mucho menor acerca del funcionamiento del recurso externo conectado que hizo la primera versión. El segundo diseño permite cambiar más fácilmente cómo el sistema interactúa con el recurso externo conectado sin cambiar y potencialmente desestabilizar el código de flujo de trabajo principal del sistema.

El segundo diseño también permite el sistema menos propenso eliminando la duplicación de la "try o abrir y finalmente/cerrar"ciclo. Cada vez que un desarrollador tiene que repetir ese código, riesgos realizar una codificación confunden podrían técnicamente funcione correctamente pero agotar los recursos y dañar las características de escalabilidad de la aplicación.

Ejecución diferida

Uno de los conceptos más importantes para comprender sobre la programación funcional es la ejecución diferida. Afortunadamente, este concepto también es relativamente sencillo. Todo significa que es una función de bloque que ha definido en línea no necesariamente se ejecuta inmediatamente. Veamos un uso práctico de ejecución diferida.

En una aplicación WPF bastante grande, uso una interfaz de marcador denominada IStartable para indicar los servicios que necesitan, bueno, iniciarse como parte el proceso de inicio de la aplicación.

 

public interface IStartable
    {
        void Start();
    }

Todos los servicios para esta aplicación particular están registrados y recuperados por la aplicación desde un contenedor de inversión de control (en este caso, el código). Al iniciarse la aplicación, tengo el siguiente fragmento de código para descubrir dinámicamente todos los servicios de la aplicación que se inicie y vuelva a iniciarlos:

 

// Find all the possible services in the main application
// IoC Container that implements an "IStartable" interface
List<IStartable> startables = container.Model.PluginTypes
    .Where(p => p.IsStartable())
    .Select(x => x.ToStartable()).ToList();
         

// Tell each "IStartable" to Start()
startables.Each(x => x.Start());

Hay tres expresiones lambda en este código. Supongamos que adjunta la copia de código fuente completo de la biblioteca de clase base de .NET a este código y intentó recorrer con el depurador. Cuando intenta ir a la ubicación, seleccione, o cada llama a, se observa que las expresiones lambda no son las siguientes líneas de código para ejecutar y que como estos métodos recorrer en iteración las estructuras internas del miembro container.Model.PluginTypes, las expresiones lambda son todos los ejecutan varias veces. Otra forma de pensar con retraso de ejecución es que cuando se invoca el método cada, está sólo indica el método cada qué hacer en cualquier momento lo que respecta a través de un objeto IStartable.

Memoization

Memoization es una técnica de optimización que se utiliza para evitar ejecutar llamadas a función costosas por volver a utilizar los resultados de la ejecución anterior con la misma entrada. Primero incluida en contacto con el memoization término respecto a la programación funcional con F #, pero en el transcurso de investigar este artículo me di cuenta que mi equipo memoization utiliza frecuentemente en el desarrollo de C#. Supongamos que necesita a menudo recuperar a algún tipo de datos financieros calculados para una región determinada con un servicio similar al siguiente:

 

public interface IFinancialDataService
    {
        FinancialData FetchData(string region);
    }

IFinancialDataService resulta ser muy lento ejecutar y los datos financieros se bastante estáticos, por lo que aplicar memoization sería muy beneficioso para la respuesta de la aplicación. Puede crear una implementación de contenedor de IFinancialDataService que implementa memoization para una clase IFinancialDataService interna, como se muestra en de figura 3.

Figura 3 implementación un interior IFinancialDataService clase

 

public class MemoizedFinancialDataService : IFinancialDataService
{
private readonly Cache<string, FinancialData> _cache;
// Take in an "inner" IFinancialDataService object that actually
// fetches the financial data
public MemoizedFinancialDataService(IFinancialDataService
innerService)
{
_cache = new Cache<string, FinancialData>(region =>
innerService.FetchData(region));
}
public FinancialData FetchData(string region)
{
return _cache[region];
}
}

La caché < TKey, TValue >clase propio es sólo un contenedor alrededor de un diccionario < TKey, TValue >objeto. de la figura 4 muestra parte de la clase Cache.

Figura 4 de la clase de caché

public class Cache<TKey, TValue> : IEnumerable<TValue> where TValue :
class
{
private readonly object _locker = new object();
private readonly IDictionary<TKey, TValue> _values;
private Func<TKey, TValue> _onMissing = delegate(TKey key)
{
string message = string.Format(
"Key '{0}' could not be found", key);
throw new KeyNotFoundException(message);
};
public Cache(Func<TKey, TValue> onMissing)
: this(new Dictionary<TKey, TValue>(), onMissing)
{
}
public Cache(IDictionary<TKey, TValue>
dictionary, Func<TKey, TValue>
onMissing)
: this(dictionary)
{
_onMissing = onMissing;
}
public TValue this[TKey key]
{
get
{
// Check first if the value for the requested key
// already exists
if (!_values.ContainsKey(key))
{
lock (_locker)
{
if (!_values.ContainsKey(key))
{
// If the value does not exist, use
// the Func<TKey, TValue> block
// specified in the constructor to
// fetch the value and put it into
// the underlying dictionary
TValue value = _onMissing(key);
_values.Add(key, value);
}
}
}
return _values[key];
}
}
}

Si está interesado en el interior de la clase Cache, puede encontrar una versión de él en varios proyectos de software de origen abierto, incluido código, StoryTeller, FubuMVC y creo, NHibernate Fluent.

El modelo de asignación o reducir

Resulta que muchas tareas comunes de desarrollo son más sencillos con técnicas de programación funcionales. En concreto, lista y las operaciones de conjunto de código son mucho más sencillas mechanically en lenguajes que admiten el modelo “ mapa o reducir ”. (En LINQ, “ mapa ” es “ seleccione ” y “ reducir ” es “ agregado ”). Piense en cómo se podría calcular la suma de una matriz de enteros. En .NET 1.1, tenía que recorrer en iteración la matriz algo así:

números de int [] = new int [] {1,2,3,4,5};
int suma = 0;
para (int i = 0;i <Numbers.Length;i ++)
{
números de suma += [i];
}

Console.WriteLine(Sum);

La ola de mejoras del lenguaje para admitir LINQ en .NET 3.5 proporciona las capacidades de mapa y reducir comunes en los lenguajes de programación funcionales. Hoy en día, el código anterior simplemente podría escribirse como:

números de int [] = new int [] {1,2,3,4,5};
int suma = numbers.Aggregate ((x, y) = >x + y);

o simplemente más como:

int suma = numbers.Sum();
Console.WriteLine(Sum);

Continuaciones

Aproximadamente, una continuación de la programación es una abstracción de algún tipo denota "qué hacer a continuación"o el "resto del cálculo". A veces resulta valiosa para finalizar la parte de un proceso de cálculo en otro momento, como en una aplicación de asistente en el que un usuario explícitamente puede permitir el paso siguiente o cancelar todo el proceso.

Vamos a saltar derecha en un ejemplo de código. Supongamos que está desarrollando una aplicación de escritorio en formularios Windows Forms o WPF. Con frecuencia deberá iniciar algún tipo de proceso de ejecución prolongada o tener acceso a un servicio externo lento desde una acción de la pantalla. Por motivos de facilidad de uso, ciertamente no desea bloquear la interfaz de usuario y hacer que no responde mientras se realiza la llamada al servicio, por lo que ejecutarse en un subproceso en segundo plano. Cuando finalmente devolver la llamada al servicio, puede que desee actualizar la interfaz de usuario con los datos vuelvan desde el servicio, pero como cualquier experimentados de Windows Forms o WPF programador sabe, puede actualizar elementos sólo en el subproceso de interfaz de usuario principal de interfaz de usuario.

Ciertamente puede utilizar la clase de BackgroundWorker en el espacio de nombres System.ComponentModel, pero prefiero un enfoque diferente según pasar expresiones lambda en un objeto CommandExecutor, representado por esta interfaz:

public interface ICommandExecutor
    {
        // Execute an operation on a background thread that
        // does not update the user interface
        void Execute(Action action);

        // Execute an operation on a background thread and
        // update the user interface using the returned Action
        void Execute(Func<Action> function);
    }

El primer método es simplemente una instrucción para realizar una actividad en un subproceso en segundo plano. El segundo método que toma en un Func < acción >es más interesante. Echemos un vistazo a cómo este método normalmente se usaría dentro del código de aplicación.

En primer lugar, suponga que va a estructurar el código de formularios Windows Forms o WPF con el formulario Supervising Controller del patrón de Model View Presenter. (Consulte msdn.microsoft.com/magazine/cc188690.aspx para obtener más información sobre el patrón MVP). En este modelo, la clase Presenter se va a ser responsable de llamar a un método de servicio de larga ejecución y utilizando los datos devueltos para actualizar la vista. Su nueva clase de moderador simplemente utilizarán la interfaz de ICommandExecutor mostrada anteriormente para controlar todo el subprocesamiento y subproceso referencias trabajo, como se muestra en de figura 5.

Figura 5 de la clase de moderador

public class Presenter
{
private readonly IView _view;
private readonly IService _service;
private readonly ICommandExecutor _executor;
public Presenter(IView view, IService service, ICommandExecutor
executor)
{
_view = view;
_service = service;
_executor = executor;
}
public void RefreshData()
{
_executor.Execute(() =>
{
var data = _service.FetchDataFromExtremelySlowServiceCall();
return () => _view.UpdateDisplay(data);
});
}
}

La clase de moderador llama ICommandExecutor.Execute pasando en un bloque que devuelve otro bloque. El bloque original invoca la llamada de servicio de larga ejecución para obtener algunos datos y devuelve un bloque de continuación que puede utilizarse para actualizar la interfaz de usuario (IView en este escenario). En este caso particular, es importante utilizar el enfoque de continuación en lugar de actualizar sólo la IView al mismo tiempo porque la actualización tiene que calcularse volver al subproceso de la interfaz de usuario.

de la figura 6 muestra la implementación concreta de la interfaz ICommandExecutor.

Figura 6 implementación concreto de ICommandExecutor

public class AsynchronousExecutor : ICommandExecutor
{
private readonly SynchronizationContext _synchronizationContext;
private readonly List<BackgroundWorker> _workers =
new List<BackgroundWorker>();
public AsynchronousExecutor(SynchronizationContext
synchronizationContext)
{
_synchronizationContext = synchronizationContext;
}
public void Execute(Action action)
{
// Exception handling is omitted, but this design
// does allow for centralized exception handling
ThreadPool.QueueUserWorkItem(o => action());
}
public void Execute(Func<Action> function)
{
ThreadPool.QueueUserWorkItem(o =>
{
Action continuation = function();
_synchronizationContext.Send(x => continuation(), null);
});
}
}

El método Execute (Func < acción >) invoca Func < acción >en un subproceso en segundo plano y, a continuación, toma la continuación (la acción devuelto por Func < acción >) y utiliza un SynchronizationContext objeto para ejecutar la continuación en el subproceso de interfaz de usuario principal.

Me gusta pasar bloques en la interfaz ICommandExecutor debido de forma poco código tambores tarda para invocar el procesamiento en segundo plano. En un encarnación anterior de este enfoque, antes de que teníamos las expresiones lambda o delegados anónimos en C#, tenía una implementación similar que utiliza las clases de patrón de Command poco similar al siguiente:

public interface ICommand
    {
        ICommand Execute();
    }

    public interface JeremysOldCommandExecutor
    {
        void Execute(ICommand command);
    }

La desventaja del enfoque anterior es que tuve que escribir clases de comandos adicionales sólo para modelar la operación en segundo plano y el código de actualización de vista. Las funciones de declaración y el constructor de clase adicional son un poco más código ceremonia que se puede eliminar con el enfoque funcional pero es más importante para mí que el enfoque funcional permite poner todo este código estrechamente relacionada en un único lugar en el moderador en lugar de tener que distribuir a través de las clases de comandos poco.

Estilo de pasar de continuación

Crear en el concepto de continuación, puede utilizar el estilo de pasar de continuación de programación para invocar un método pasando un aviso de continuación en lugar de esperar para el valor devuelto del método. El método que acepta la continuación es responsable de decidir si y cuándo llamar a la continuación.

En mi proyecto de MVC Web actual, hay docenas de acciones de controlador guardar actualizaciones procedentes del usuario enviados desde el explorador del cliente mediante una llamada de AJAX a un objeto de entidad del dominio. La mayoría de estas acciones controlador simplemente invoca nuestra clase de depósito para guardar la entidad ha cambiado, pero otras acciones utilizan otros servicios para realizar el trabajo de persistencia. (Consulte mi artículo de abril de de MSDN Magazine en de msdn.microsoft.com/magazine/dd569757.aspx para obtener más información acerca de la clase de repositorio.)

El flujo de trabajo básico de estas acciones de controlador es coherente:

  1. Validar la entidad de dominio y registre los errores de validación.
  2. Si hay errores de validación, devolver una respuesta al cliente que indica que la operación no se pudo realizar e incluya los errores de validación para su presentación en el cliente.
  3. Si no hay errores de validación, conservar la entidad de dominio y devolver una respuesta al cliente que indica que la operación se realizó correctamente.

¿Qué nos gustaría hacer es centralizar el flujo de trabajo básica pero permitirá que cada acción individual de controlador para modificar cómo se realiza la persistencia real. Hoy es hacerlo mi equipo heredando de una CrudController < T >superclase con gran cantidad de métodos de la plantilla que cada subclase puede reemplazar para agregar o cambiar el comportamiento básico. Esta estrategia averiguar bien al principio, pero rápidamente es desglosar como han aumentado las variaciones. Ahora mi equipo se va a mover a mediante código de estilo continuación pasar por tener nuestras acciones controlador delegar a algo parecido a la siguiente interfaz:

 

public interface IPersistor
    {
        CrudReport Persist<T>(T target, Action<T> saveAction);
        CrudReport Persist<T>(T target);
    }

Una acción típica controlador diría IPersistor para realizar el flujo de trabajo CRUD básico y proporcione un aviso de continuación IPersistor utiliza para guardar realmente el objeto de destino. de la figura 7 muestra una acción de controlador de ejemplo invoca IPersistor pero utiliza un servicio diferente que nuestro repositorio básico para la persistencia real.

Figura 7 una acción de controlador de ejemplo

public class SolutionController
{
private readonly IPersistor _persistor;
private readonly IWorkflowService _service;
public SolutionController(IPersistor persistor, IWorkflowService
service)
{
_persistor = persistor;
_service = service;
}
// UpdateSolutionViewModel is a data bag with the user
// input from the browser
public CrudReport Create(UpdateSolutionViewModel update)
{
var solution = new Solution();
// Push the data from the incoming
// user request into the new Solution
// object
update.ToSolution(solution);
// Persist the new Solution object, if it's valid
return _persistor.Persist(solution, x => _service.Create(x));
}
}

Creo que lo importante que tenga en cuenta aquí es que IPersistor propio es decidir si y cuándo se llamará la continuación suministrado por SolutionController. de la figura 8 muestra una implementación de IPersistor de ejemplo.

Figura 8 en una implementación de IPersistor

public class Persistor : IPersistor
{
private readonly IValidator _validator;
private readonly IRepository _repository;
public Persistor(IValidator validator, IRepository repository)
{
_validator = validator;
_repository = repository;
}
public CrudReport Persist<T>(T target, Action<T> saveAction)
{
// Validate the "target" object and get a report of all
// validation errors
Notification notification = _validator.Validate(target);
// If the validation fails, do NOT save the object.
// Instead, return a CrudReport with the validation errors
// and the "success" flag set to false
if (!notification.IsValid())
{
return new CrudReport()
{
Notification = notification,
success = false
};
}
// Proceed to saving the target using the Continuation supplied
// by the client of IPersistor
saveAction(target);
// return a CrudReport denoting success
return new CrudReport()
{
success = true
};
}
public CrudReport Persist<T>(T target)
{
return Persist(target, x => _repository.Save(x));
}
}

Escribir menos código

Francamente, originalmente elegí este tema porque estaba interesado en aprender más acerca de programación funcional y cómo se puede aplicar incluso dentro de C# o Visual Basic. En el curso de escribir este artículo, he aprendido mucho sobre técnicas de programación funcionales lo útiles puede estar en las tareas y cotidiano. La conclusión más importante ha llegado, y lo he intentado transmitir aquí, se compara con otras técnicas, enfoques de programación funcionales a menudo puede conducir a escribir menos código y a menudo más código declarativo para algunas tareas, y eso casi siempre es bueno.

Jeremy MillerMVP de Microsoft para C#, también es el autor de la herramienta de código (de structuremap.sourceforge.net) de código abierto para la inserción de dependencia con .NET y la herramienta de (storyteller.tigris.org de) de StoryTeller próxima para las pruebas FIT en. NET. Visite su blog, el desarrollador de árbol de la sombra, en codebetter.com/blogs/jeremy.miller, parte del sitio CodeBetter.