简化表达式选择器

4

目前我正在使用一个名为"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表达式的()=>部分的情况下调用ArgumentNotNull,只需调用Ensure.ArgumentNotNull(arg)就能获取传递的参数的名称,而无需特别传递名称。


2
你可以一直等待C# 6.0版本;那时你就能使用 nameof 表达式了。 - InBetween
2
@DStanley:并不是说它很麻烦,只是它看起来不够简洁而已。总之,这个问题本来就是一个推测性的问题,我之所以问是因为我不知道答案。 - bizzehdee
你有没有考虑使用 Fody 来进行 AOP,以在构建时添加 Ensure。或者,你可以考虑使用代码合同和静态代码分析来“证明”参数永远不会为 null。 - Aron
@Aron:没有,因为那不是我想要实现的目标。 - bizzehdee
@bizzehdee 你应该看一下这个... https://github.com/Fody/NullGuard - Aron
4个回答

2
有没有一种方法可以在不需要lambda表达式的() =>部分的情况下调用ArgumentNotNull,只需调用Ensure.ArgumentNotNull(arg),并仍然能够获取传递的参数名称?
我认为不可能,因为值没有元数据来确定它是传递的参数、变量还是文字。该值并不总是参数 - 没有任何阻止您调用Ensure.ArgumentNotNull(null);的东西。

这将在C# 6.0中通过nameof表达式进行更改。我认为所需的基础设施已经存在一段时间了,但语言从未利用过它。 - InBetween
3
@InBetween 它会吗?你仍然需要传递第二个参数 - Ensure.ArgumentNotNull(arg, nameof(arg));nameof 并没有魔法来告诉函数传入的值在调用函数中被命名为 arg - D Stanley
@ChrisMcKelt,原帖的问题中包含了实现此功能的代码。 - Servy
@DStanley 非常正确,我承认错误,你仍然需要两个参数。 - InBetween
但价值在于,如果变量被重命名,并且代码编译(这使我们可以假设该变量在所有位置都被重命名了),那么第二个参数将始终是正确的,而不是可能保留旧的和不正确的参数名称。 - ErikE

0

这个有效

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.'

使用简化的lambda表达式就可以解决问题。

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();

        }

    }

1
OP 问如何在不传递 lambda 的情况下获取参数名称。 - D Stanley
1
是的,但是OP已经有了一个使用lambda的方法(虽然比您的方法更简陋),但希望能够直接传递值。 - D Stanley
@DStanley更新了答案,提供了一个可行的解决方案(不使用lambda表达式),这在某种程度上受到了这里的启发 - https://dev59.com/eHI-5IYBdhLWcg3wtaxq?rq=1 - Chris McKelt
栈方法,如果我没记错的话,不一定适用于发布版本。 - galenus
为此,您应该在生产环境中可以访问源代码。 - galenus
显示剩余2条评论

0

我不知道除了Fody.NullGuard(仅支持opt-out模型)和其他AOP框架(如PostSharp)之外,你能否在没有一定程度的代码混乱的情况下使用任何机制。

然而,与表达式树相比,匿名类型是一个相对好的选择,因为它们在运行时性能方面有很大的优势,并且在语法方面也更加灵活。但是需要注意以下几点:

  • API的使用者只用于参数断言
  • C#中匿名类型的实现细节不会有显著变化

API的使用方式示例如下:

void SomeMethod(object arg1, string arg2, List<int> arg3)
{
    new { arg1, arg2, arg3 }.ShouldNotBeNull();
    ....

实现过程太大无法在此展示,可以在这个gist中找到。此外,它可以进行范围验证等扩展。


-1
如果你担心在调用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)
    {
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接