una que monada leibniz filosofia espiritual c# extension-methods monads

que - ¿Uso malvado de los métodos de mónada y extensión Maybe en C#?



que es override en c# (8)

editar 2015 Esta pregunta y sus respuestas ya no son relevantes. Se preguntó antes del advenimiento de C # 6, que tiene el opertor de propagación nulo (?.), Que obvia las soluciones de hacky discutidas en esta pregunta y respuestas posteriores. A partir de 2015, en C # ahora debería usar Form.ActiveForm? .ActiveControl? .Name.

He estado pensando en el problema de propagación nula en .NET, que a menudo conduce a un código feo y repetido como este:

Intento # 1 código habitual:

string activeControlName = null; var activeForm = Form.ActiveForm; if (activeForm != null) { var activeControl = activeForm.ActiveControl; if(activeControl != null) { activeControlname = activeControl.Name; } }

Han habido algunas discusiones sobre StackOverflow sobre una mónada Maybe <T>, o usando algún tipo de método de extensión "if not null":

Intento # 2, método de extensión:

// Usage: var activeControlName = Form.ActiveForm .IfNotNull(form => form.ActiveControl) .IfNotNull(control => control.Name); // Definition: public static TReturn IfNotNull<TReturn, T>(T instance, Func<T, TReturn> getter) where T : class { if (instance != null ) return getter(instance); return null; }

Creo que esto es mejor, sin embargo, hay un poco de confusión sintáctica con las repetidas "IfNotNull" y las lambdas. Ahora estoy considerando este diseño:

Intento # 3, Tal vez <T> con el método de extensión

// Usage: var activeControlName = (from window in Form.ActiveForm.Maybe() from control in window.ActiveControl.Maybe() select control.Name).FirstOrDefault(); // Definition: public struct Maybe<T> : IEnumerable<T> where T : class { private readonly T instance; public Maybe(T instance) { this.instance = instance; } public T Value { get { return instance; } } public IEnumerator<T> GetEnumerator() { return Enumerable.Repeat(instance, instance == null ? 0 : 1).GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } public static class MaybeExtensions { public static Maybe<T> Maybe<T>(this T instance) where T : class { return new Maybe<T>(instance); } }

Mi pregunta es : ¿es este un mal uso de los métodos de extensión? ¿Es mejor que los viejos controles nulos?


Aunque soy un fanático de los métodos de extensión, no creo que esto sea realmente útil. Todavía tienes la repetición de las expresiones (en la versión monádica), y solo significa que tienes que explicar Maybe a todos. La curva de aprendizaje adicional no parece tener suficiente beneficio en este caso.

La versión IfNotNull al menos se las arregla para evitar la repetición, pero creo que todavía es demasiado larga sin ser más clara.

Tal vez algún día obtengamos un operador de desreferenciación seguro de nulos ...

Solo como un lado, mi método de extensión semi-mal favorito es:

public static void ThrowIfNull<T>(this T value, string name) where T : class { if (value == null) { throw new ArgumentNullException(name); } }

Eso te permite cambiar esto:

void Foo(string x, string y) { if (x == null) { throw new ArgumentNullException(nameof(x)); } if (y == null) { throw new ArgumentNullException(nameof(y)); } ... }

dentro:

void Foo(string x, string y) { x.ThrowIfNull(nameof(x)); y.ThrowIfNull(nameof(y)); ... }

Todavía hay una desagradable repetición del nombre del parámetro, pero al menos está más ordenado. Por supuesto, en .NET 4.0 utilizaría Code Contracts, que es sobre lo que estoy destinado a escribir ahora mismo ... es una gran forma de evitar el trabajo;)


Claro, IF 2-anidados original es mucho más legible que otras opciones. Pero sugiriendo que quiere resolver el problema de manera más general, aquí hay otra solución:

try { var activeForm = Form.ActiveForm; assumeIsNotNull(activeForm); var activeControl = activeForm.ActiveControl; assumeIsNotNull(activeControl); var activeControlname = activeControl.Name; } catch (AssumptionChainFailed) { }

dónde

class AssumptionChainFailed : Exception { } void assumeIsNotNull(object obj) { if (obj == null) throw new AssumptionChainFailed(); }


En caso de que se trate de C # 6.0 / VS 2015 y superior, ahora tienen una solución integrada para la propagación nula:

string ans = nullableString?.Length.ToString(); // null if nullableString == null, otherwise the number of characters as a string.


Es interesante que mucha gente escoja independientemente el nombre IfNotNull , para esto en C # - ¡debe ser el nombre más sensato posible! :)

El primero que encontré en SO: Posibles trampas de usar esta taquigrafía (basada en el método de extensión)

Mi único (en la ignorancia de lo anterior): Pipe forward en C #

Otro ejemplo más reciente: ¿Cómo verificar nulos en una expresión lambda profunda?

Hay un par de razones por las que el método de extensión IfNotNull puede ser impopular.

  1. Algunas personas son inflexibles en que un método de extensión debería lanzar una excepción si es this parámetro null . No estoy de acuerdo si el nombre del método lo deja en claro.

  2. Las extensiones que se aplican de manera demasiado amplia tenderán a complicar el menú de autocompletado. Esto se puede evitar mediante el uso adecuado de espacios de nombres para que no molesten a las personas que no los quieren.

He jugado con el enfoque IEnumerable también, solo como un experimento para ver cuántas cosas podría torcer para que se ajusten a las palabras clave de Linq, pero creo que el resultado final es menos legible que el encadenamiento IfNotNull o el código imperativo sin IfNotNull .

Terminé con una clase de Maybe autónoma con un método estático (no es un método de extensión) y eso funciona muy bien para mí. Pero luego, trabajo con un pequeño equipo, y mi próximo colega más veterano está interesado en la programación funcional y lambdas, y así sucesivamente, por lo que no se deja intimidar por ello.


La muestra inicial funciona y es la más fácil de leer de un vistazo. ¿Realmente hay una necesidad de mejorar en eso?


La solución IfNotNull es la mejor (hasta que el equipo C # nos dé un operador de desreferenciación seguro de nulos).


No estoy demasiado loco por ninguna de las soluciones. Lo que estaba mal con una versión más corta del original:

string activeControlName = null; if (Form.ActiveForm != null) if (Form.ActiveForm.ActivControl != null) activeControlname = activeControl.Name;

Si no fuera así, consideraría escribir un objeto NotNullChain o FluentNotNull que pueda encadenar algunas pruebas no nulas en una fila. Estoy de acuerdo en que el método de extensión IfNotNull que actúa sobre un nulo parece un poco extraño, aunque los métodos de extensión son solo azúcar sintáctico.

Creo que la respuesta de Mark Synowiec podría ser genérica.

En mi humilde opinión, creo que el equipo central de C # debería considerar este "problema", aunque creo que hay cosas más importantes que abordar.


Si desea que un método de extensión reduzca los valores anidados que tiene, puede intentar algo como esto:

public static object GetProperty(this object o, Type t, string p) { if (o != null) { PropertyInfo pi = t.GetProperty(p); if (pi != null) { return pi.GetValue(o, null); } return null; } return null; }

entonces en tu código simplemente harías:

string activeControlName = (Form.ActiveForm as object) .GetProperty(typeof(Form),"ActiveControl") .GetProperty(typeof(Control),"Name");

No sé si me gustaría usarlo a menudo debido a la lentitud de la reflexión, y realmente no creo que sea mucho mejor que la alternativa, pero debería funcionar, independientemente de si tocas un nulo a lo largo del camino...

(Nota: podría haber mezclado esos tipos) :)