代码之家  ›  专栏  ›  技术社区  ›  Judah Gabriel Himango

在C中可能使用了Monad和扩展方法。

  •  35
  • Judah Gabriel Himango  · 技术社区  · 15 年前

    编辑2015 这个问题及其答案不再相关。它是在C 6出现之前被问到的,C 6具有空的传播操作符(?这就避免了这个问题和随后的答案中讨论的那些老掉牙的解决方法。截至2015年,在C中,您现在应该使用Form.ActiveForm?ActiveControl?名字。


    我一直在考虑.NET中的空传播问题,这通常会导致像下面这样的丑陋、重复的代码:

    尝试1常用代码:

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

    关于stackoverflow,有一些关于maybe<t>monad的讨论,或者使用某种“if not null”扩展方法:

    尝试2,扩展方法:

    // 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;
    }
    

    不过,我认为这样做更好,因为重复出现的“ifnotnull”和lambda在语法上有点混乱。我正在考虑这个设计:

    尝试3,可能使用扩展方法

    // 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);
        }
    }
    

    我的问题是 :这是对扩展方法的恶意滥用吗?它比以前的空支票好吗?

    8 回复  |  直到 8 年前
        1
  •  18
  •   Community Dunja Lalic    7 年前

    有趣的是,有这么多人独立地选择了这个名字 IfNotNull ,对于C-它必须是最合理的名称!:)

    我发现的最早的一个,所以: Possible pitfalls of using this (extension method based) shorthand

    我的那个(不知道上面说的话): Pipe forwards in C#

    另一个最近的例子: How to check for nulls in a deep lambda expression?

    有几个原因可以解释 IFNOT NULL 扩展方法可能不受欢迎。

    1. 有些人坚持认为,如果扩展方法 this 参数是 null . 我不同意方法名是否清楚。

    2. 应用范围太广的扩展会导致自动完成菜单混乱。但是,通过正确地使用名称空间可以避免这种情况,这样就不会让不需要名称空间的人感到恼火。

    我一直在玩 IEnumerable 同样的方法,就像一个实验,看看我可以扭曲多少东西来适应linq关键字,但是我认为最终的结果比 IFNOT NULL 链接或原始命令代码。

    我最终得到了一个简单的自给自足的 Maybe 用一个静态方法(不是扩展方法)初始化,这对我来说非常好。但后来,我和一个小团队合作,我的下一个最资深的同事对函数编程和lambda等很感兴趣,所以他不会被它耽搁。

        2
  •  14
  •   Jon Skeet    8 年前

    虽然我很喜欢扩展方法,但我认为这并没有什么帮助。你仍然有表达式的重复(在单态版本中),这意味着你必须解释 Maybe 给大家。在这种情况下,增加的学习曲线似乎没有足够的好处。

    这个 IfNotNull 版本至少避免了重复,但我认为它仍然有点太长了,实际上还没有变得清晰。

    也许有一天我们会得到一个空安全解引用操作符…


    另外,我最喜欢的半邪恶扩展方法是:

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

    这样你就可以:

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

    进入:

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

    参数名仍然有令人讨厌的重复,但至少它更整洁。当然,在.NET 4.0中,我会使用代码契约,这就是我现在要写的内容…栈溢出是很好的避免工作;)

        3
  •  1
  •   Mark Synowiec    15 年前

    如果希望扩展方法减少嵌套if,可以尝试如下操作:

    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;
    }
    

    所以在你的代码中你只需要:

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

    我不知道我是否愿意经常使用它,因为思考的缓慢,我并不真的认为这比选择更好,但它应该工作,无论你是否击中一个空的一路上…

    (注意:我可能把这些类型弄混了):)

        4
  •  1
  •   thefellow3j    9 年前

    如果您处理的是C 6.0/Vs2015及更高版本,那么它们现在有了一个用于零传播的内置解决方案:

    string ans = nullableString?.Length.ToString(); // null if nullableString == null, otherwise the number of characters as a string.
    
        5
  •  0
  •   HeathenWorld    15 年前

    初始样本可以工作,并且是最容易阅读的一瞥。这方面真的需要改进吗?

        6
  •  0
  •   jeroenh    15 年前

    ifnotnull解决方案是最好的(直到c团队给我们一个空安全的解引用操作符,也就是说)。

        7
  •  0
  •   BuddyJoe    14 年前

    我对这两种解决方案都不太着迷。原始版本的ashorter有什么问题:

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

    如果不是这样,那么我将考虑编写一个NotNullChain或FluentNotNull对象,而不是在一行中链接几个非空测试。我同意ifnotnull扩展方法对空值的作用有点奇怪——尽管扩展方法只是语法上的糖分。

    我认为MarkSynowiec的答案可能是通用的。

    imho,我认为核心团队应该关注这个“问题”,尽管我认为还有更大的事情要解决。

        8
  •  0
  •   danbst    12 年前

    当然,原始的2嵌套if比其他选项更可读。但建议您更一般地解决问题,这里是另一个解决方案:

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

    哪里

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