【问题标题】:How to wrap Expression<Func<T, TProperty>> with TryCatch that returns null?如何用返回 null 的 TryCatch 包装 Expression<Func<T, TProperty>>?
【发布时间】:2021-08-27 00:44:04
【问题描述】:

我正在尝试验证我的 Unity MonoBehavior,以便在脚本设置不正确时更加明显。我从我所做的其他 C# 工作中了解到 FluentValidation,因此我为 Unity 设置了 Nuget 并安装了它。

问题在于,当 MonoBehavior 上的 GameObject 未初始化时,由于 Unity 的行为覆盖,FluentValidation 的表达式会抛出错误而不是返回 null。

我试图通过创建一个扩展方法来解决这个问题,该方法将捕获表达式中抛出的任何异常并返回 null。我不熟悉使用 Expression 类构建表达式,但我不确定该怎么做。运行它会导致编辑器控制台中出现以下错误:

InvalidOperationException: No coercion operator is defined between types 'System.Func`2[DialogController,UnityEngine.GameObject]' and 'UnityEngine.GameObject'.
System.Linq.Expressions.Expression.GetUserDefinedCoercionOrThrow (System.Linq.Expressions.ExpressionType coercionType, System.Linq.Expressions.Expression expression, System.Type convertToType) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
System.Linq.Expressions.Expression.Convert (System.Linq.Expressions.Expression expression, System.Type type, System.Reflection.MethodInfo method) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
System.Linq.Expressions.Expression.Convert (System.Linq.Expressions.Expression expression, System.Type type) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
ValidationExtensions.SafeRuleFor[T,TProperty] (FluentValidation.AbstractValidator`1[T] validator, System.Linq.Expressions.Expression`1[TDelegate] expression) (at Assets/Scripts/ValidationExtensions.cs:12)
DialogControllerValidation.WhenAsync (System.Func`3[T1,T2,TResult] predicate, System.Action action) (at Assets/Scripts/DialogControllerValidation.cs:10)
DialogController.OnValidate () (at Assets/Scripts/DialogController.cs:170)

这是我的 MonoBehavior:

public class DialogController: MonoBehavior {
    public GameObject content;
    
    private void OnValidate()
    {
        new DialogControllerValidation().ValidateAndThrow(this);
    }
}

这是我的验证器类:

public class DialogControllerValidation : AbstractValidator<DialogController>
{
    public DialogControllerValidation()
    {
        this.SafeRuleFor(x => x.content).NotNull();
    }
}

这是我的扩展方法:

public static class ValidationExtensions
{
    public static IRuleBuilderInitial<T, TProperty> SafeRuleFor<T, TProperty>(this AbstractValidator<T> validator,
        Expression<Func<T, TProperty>> expression)
    {
        var tryExpression = Expression.TryCatch(
            Expression.Block(typeof(TProperty), Expression.Convert(expression, typeof(TProperty))),
            Expression.Catch(typeof(TProperty), Expression.Constant(null))
        );
        return validator.RuleFor(Expression.Lambda<Func<T, TProperty>>(
                tryExpression,
                Expression.Parameter(typeof(T), "t")
            )
        );
    }
}

编辑:如果我将Expression.Convert(expression, typeof(TProperty)) 替换为expression,即:

public static class ValidationExtensions
{
    public static IRuleBuilderInitial<T, TProperty> SafeRuleFor<T, TProperty>(this AbstractValidator<T> validator,
        Expression<Func<T, TProperty>> expression)
    {
        var tryExpression = Expression.TryCatch(
            Expression.Block(typeof(TProperty), expression),
            Expression.Catch(typeof(TProperty), Expression.Constant(null))
        );
        return validator.RuleFor(Expression.Lambda<Func<T, TProperty>>(
                tryExpression,
                Expression.Parameter(typeof(T), "t")
            )
        );
    }
}

那么我得到的错误是

ArgumentException: Argument types do not match
System.Linq.Expressions.Expression.BlockCore (System.Type type, System.Collections.ObjectModel.ReadOnlyCollection`1[T] variables, System.Collections.ObjectModel.ReadOnlyCollection`1[T] expressions) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
System.Linq.Expressions.Expression.Block (System.Type type, System.Collections.Generic.IEnumerable`1[T] variables, System.Collections.Generic.IEnumerable`1[T] expressions) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
System.Linq.Expressions.Expression.Block (System.Type type, System.Collections.Generic.IEnumerable`1[T] expressions) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
System.Linq.Expressions.Expression.Block (System.Type type, System.Linq.Expressions.Expression[] expressions) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
ValidationExtensions.SafeRuleFor[T,TProperty] (FluentValidation.AbstractValidator`1[T] validator, System.Linq.Expressions.Expression`1[TDelegate] expression) (at Assets/Scripts/ValidationExtensions.cs:12)
DialogControllerValidation.WhenAsync (System.Func`3[T1,T2,TResult] predicate, System.Action action) (at Assets/Scripts/DialogControllerValidation.cs:10)
DialogController.OnValidate () (at Assets/Scripts/DialogController.cs:170)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&)

【问题讨论】:

  • "在类型 'System.Func``2[DialogController,UnityEngine.GameObject]' 和 'UnityEngine.GameObject' 之间没有定义强制运算符",因为这就是您在这里所要求的; Expression.Convert(expression, typeof(TProperty))。您的意思是调用该方法吗?
  • @JeremyLakeman 我在看this SO 答案。如果我不包含Expression.Convert() 而只使用expression,则会收到“参数类型不匹配”错误。 (请参阅我的编辑。)我的意图是使用 Expression.Convert() 将两个表达式更改为具有相同的类型。
  • 你可能想要的是重用expression.Body & expression.Parameters。但是控制流和返回值有点奇怪(例如stackoverflow.com/questions/13687914/…)。

标签: c# unity3d fluentvalidation


【解决方案1】:

我没有弄清楚如何使用Expressions 以通用方式完成此任务,但我确实知道如何专门针对GameObjects 解决此问题,这是我试图解决的问题的原因.

一般的想法是,只要表达式中只有一条语句,您就可以在 Expression 中运行方法。

这是我想出的扩展方法:

public static class ValidationExtensions
{
    public static IRuleBuilderInitial<T, GameObject> SafeRuleFor<T>(this AbstractValidator<T> validator,
        Func<T, GameObject> func)
    {
        return validator.RuleFor(x => SafeWrap(func(x)));
    }

    private static GameObject SafeWrap(GameObject gameObject)
    {
        return gameObject ? gameObject : null;
    }
}

如果您希望将输入参数保留为表达式,可以将func(x) 替换为expression.Compile().Invoke(x)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-27
    相关资源
    最近更新 更多