简化表达式选择器

本文关键字:选择器 表达式 | 更新日期: 2023-09-27 18:29:29

目前我正在一个名为"确保"的类中使用这段代码,它本质上是一个静态方法的快捷类,我使用它来简化抛出异常的过程,所以我不必经常写至少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),仍然可以获得传递的参数的名称,而不必专门传递名称。

简化表达式选择器

有没有一种方法可以在不需要lambda的() =>部分的情况下调用ArgumentNotNull,只需调用Ensure.ArgumentNotNull(arg),仍然可以获得传递给的参数的名称

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

这适用于

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

除了Aron提到的Fody.NullGuard(仅限选择退出模型)和其他AOP框架(PostSharp)之外,我不知道有任何机制可以在没有一定程度代码混乱的情况下用于此。

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

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

API的使用看起来像:

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

实现太大了,无法在这里显示,所以可以在这个要点中找到它。此外,它还可以扩展到范围验证等。

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