简化表达式选择器

Posted

技术标签:

【中文标题】简化表达式选择器【英文标题】:Simplifying an expression selector 【发布时间】:2015-01-03 02:44:06 【问题描述】:

目前我在一个名为“Ensure”的类中使用这段代码,它本质上是一个静态方法的快捷类,我用来使抛出异常更容易,所以我不必经常写出至少 3 个行做一个例外,它总是可以在 1 行完成。

    [DebuggerHidden, DebuggerStepThrough]
    public static void ArgumentNotNull(object argument, string name)
    
        if (argument == null)
        
            throw new ArgumentNullException(name, "Cannot be null");
        
    

    [DebuggerHidden, DebuggerStepThrough]
    public static void ArgumentNotNull<T>(Expression<Func<T>> expr)
    
        var e = (MemberExpression)expr.Body;

        var val = GetValue<T>(e);

        ArgumentNotNull(val, e.Member.Name);
    

我的问题是,目前在致电Ensure.ArgumentNotNull 时,我要么必须这样做:

Ensure.ArgumentNotNull(arg, "arg");

Ensure.ArgumentNotNull(() => arg);

因为我需要名称来解释哪个参数导致了异常本身的异常。

有没有一种方法可以在不需要 lambda 的 () =&gt; 部分的情况下调用 ArgumentNotNull 并且只需调用 Ensure.ArgumentNotNull(arg) 并且仍然能够获取传递的参数的名称,而无需特别指定也传递名称。

【问题讨论】:

你可以一直等待C# 6.0;你会有nameof 表达式。 @DStanley:这不是负担,只是看起来不够干净,而且这是一个推测性的问题,我问是因为我不知道答案。 您是否考虑过使用Fody 进行AOP 在构建类型中添加 Ensure。或者,您可以考虑使用代码协定和静态代码分析来“证明”arg 永远不会为空。 @Aron:不,我没有,因为那不是我想要达到的目标。 @bizzehdee 你应该看看这个...github.com/Fody/NullGuard 【参考方案1】:

有没有一种方法可以在不需要 lambda 的 () =&gt; 部分的情况下调用 ArgumentNotNull 并且只需调用 Ensure.ArgumentNotNull(arg) 并且仍然能够获取传递的参数的名称

我对此表示怀疑,因为值没有元数据来确定它是传入的参数、变量还是文字。这个值并不总是一个参数——没有什么能阻止你调用Ensure.ArgumentNotNull(null);

【讨论】:

这将在 C# 6.0 中使用 nameof 表达式进行更改。我认为所需的基础设施已经存在了一段时间,但语言从未利用过它。 @InBetween 会吗?您仍然需要传入第二个参数 - Ensure.ArgumentNotNull(arg, nameof(arg)); nameof 没有魔法可以告诉函数传入的值在 在调用函数中命名为 arg @ChrisMcKelt OP 的问题包含执行此操作的代码。 @DStanley 非常正确,我的立场是正确的,你仍然需要两个参数。 但是这里的值是,如果变量被重命名,并且代码编译(这允许我们假设变量在所有位置都被重命名),那么第二个参数将始终正确,而不是可能会保留旧的和不正确的参数名称。【参考方案2】:

这行得通

public static void ArgumentNotNull(object argument)
    
        StackFrame stackFrame = new StackTrace(true).GetFrame(1);
        string fileName = stackFrame.GetFileName();
        int lineNumber = stackFrame.GetFileLineNumber();
        var file = new System.IO.StreamReader(fileName);
        for (int i = 0; i < lineNumber - 1; i++)
            file.ReadLine();
        string varName = file.ReadLine().Split(new char[]  '(', ')' )[1];


        if (argument == null)
        
            throw new ArgumentNullException(varName, "Cannot be null");
        
    

OP 问题的替代答案

  'and still be able to get the name of the argument that was passed, without having to specifically pass the name as well.'

简化的 lamdba 可以解决问题。

myObject.ArgumentNotNull(x=>x.SomeProperty);  -- prop checking
myObject.ArgumentNotNull(x=>x);  -- objchecking

[DebuggerHidden, DebuggerStepThrough]
    public static void ArgumentNotNull<T>(this T obj, Expression<Func<T, object>> expr = null)
    
        if (obj == null) throw new NullReferenceException();

        var body = expr.Body as MemberExpression;

        if (body == null)
        
            var ubody = (UnaryExpression)expr.Body;
            body = ubody.Operand as MemberExpression;
        

        if (body != null)
        
            var property = body.Member as PropertyInfo;
            if (property == null) throw;
            if (obj.GetType().GetProperty(property.Name).GetValue(obj, null) == null) throw new NullReferenceException();

        
        else
        
            var ubody = (UnaryExpression)expr.Body;
            var property = ubody.Operand as MemberExpression;
            if (property != null)
                props[property.Member.Name] = obj.GetType()
                    .GetProperty(property.Member.Name)
                    .GetValue(obj, null);
            if (obj.GetType().GetProperty(property.Member.Name).GetValue(obj, null) == null) throw new NullReferenceException();

        

    

【讨论】:

在这里写下如何通过 lambda 表达式获取属性名称/值的好文章 - joelabrahamsson.com/… OP 询问如何在传入 lambda 的情况下获取参数名称。 是的,但是 OP 已经有一种使用 lambda 的方法(尽管比你的更基本),但希望能够只传递值。 @DStanley 使用有效的解决方案(无 lambdas)更新了答案 - 部分灵感来自这里 - ***.com/questions/1718037/… 堆栈方法,如果我没记错的话,不一定适用于发布版本。【参考方案3】:

除了 Aron 提到的 Fody.NullGuard(仅限选择退出模型)和其他 AOP 框架 (PostSharp) 之外,我不知道您可以为此使用任何机制而不造成一定程度的代码混乱。

但是,您可以使用匿名类型,而不是使用会导致显着的运行时性能损失并且在语法方面受到限制的表达式树。这种方法在性能方面相对较好,有几个假设:

API 的用户将仅将其用于参数断言 C# 中匿名类型实现的细节不会发生重大变化

API 的用法如下所示:

void SomeMethod(object arg1, string arg2, List<int> arg3)

    new  arg1, arg2, arg3 .ShouldNotBeNull();
    ....

实现太大,这里无法展示,所以可以在this gist 中找到。此外,它可以扩展为范围验证等。

【讨论】:

【参考方案4】:

如果您担心在调用 throw new ArgumentNullException("argName"); 时出错,您真的想使用 Fody.NullGuard 而不是当前的解决方案。

你像这样使用 Fody.NullGuard

public class Sample

    public void SomeMethod(string arg)
    
        // throws ArgumentNullException if arg is null.
    

    public void AnotherMethod([AllowNull] string arg)
    
        // arg may be null here
    

编译后,Fody 会重写 IL,使其功能与

public class Sample

    public void SomeMethod(string arg)
    
        if(arg == null) 
            throws new  ArgumentNullException("arg");
    

    public void AnotherMethod(string arg)
    
    

【讨论】:

以上是关于简化表达式选择器的主要内容,如果未能解决你的问题,请参考以下文章

Android 图片选择器,丰富的配置选项,极大程度的简化使用

CSS 正则表达式 id 选择器与类选择器的性能

jQuery选择器介绍:基本选择器层次选择器过滤选择器表单选择器

css选择器分类及运用

Javascript - Jquery选择器

jQuery选择器