为什么 Resharper 无法判断使用了初始化

Why Resharper can't tell initialization is used

我在编写单元测试时偶然发现了 Resharper 的建议。

Value assigned is not used by any execution path.

在下面的代码片段中。

[Test]
[TestCase((int)OddsRoundingModes.Floor)]
public void GetBaseOddsRoundingMode_WithCorrectRoundingMode_ShouldReturnCorrectRoundingMode(int oddsRoundingMode)
{
    // Arrange
    var oddsRoundingModeStr = oddsRoundingMode.ToString(); // <-- suggestion here
    var mock = new Mock<IConstantsStorage>();
    var oddsRoundingConfiguration = new OddsRoundingConfiguration(mock.Object);
    mock.Setup(h => h.TryGetConstant(It.IsAny<string>(), It.IsAny<int>(), out oddsRoundingModeStr))
        .Returns(true);

    // Act
    var roundingMode = oddsRoundingConfiguration.GetBaseOddsRoundingMode(0);

    // Assert
    Assert.AreNotEqual(roundingMode, OddsRoundingModes.None);
}

但是当我将其更改为在声明时未初始化时,模拟未正确设置并且测试失败,因为 oddsRoundingModeStr 未初始化并且模拟 return 为 null。

为什么 Resharper 看不到这个?

编辑:

public bool TryGetConstant(string name, int siteId, out string value)
{
    value = RetrieveConstant(_constantsModel, name, siteId);

    return value != null;
}

private string RetrieveConstant<T>(IConstantsModel<T> model, string constName, int siteId)
    where T : IConstant, new()
{
    if (model.Constants.TryGetValue(constName, out List<T> values))
    {
        var constant = values.FirstOrDefault(v => v.Name == constName && v.SiteIds.Contains(siteId));
        if (constant != null)
        {
            return constant.ConstantValue;
        }
    }

    return null;
}

Setup 接受表达式树 - Moq 分析该表达式树以创建最小起订量。在这种情况下,你基本上是说 Moq 应该创建 IConstantsModel 的实现,它接受任何字符串、任何 int、returns true 和你在 oddsRoundingModeStr 中提供的 returns 值作为 out 参数。所以在分析这个表达式树时,Moq 会提取 oddsRoundingModeStr 的实际值(捕获并存储在 compiler-generated class 的字段中)并且确实会使用它。 Resharper 无法意识到这一点,因此照常提供警告。

如何从表达式树中提取 out 变量值的小示例:

class Program {
    static void Main(string[] args) {
        int result = 2; // gives warning from your question
        var back = ExtractOutValue(s => int.TryParse(s, out result));           
        Debug.Assert(back == result);
    }

    static int ExtractOutValue(Expression<Action<string>> exp) {
        var call = (MethodCallExpression)exp.Body;            
        var arg = (MemberExpression) call.Arguments[1];
        return (int) ((FieldInfo)arg.Member).GetValue(((ConstantExpression)arg.Expression).Value);            
    }        
}

按照正常的 C# 语义,您将该变量初始化为的值是无关紧要的,因为 out 在为其分配新值之前无法读取数据。因此,resharper 通知是合适的。

我看到使用此代码可以实现非标准语义的几种方法:

  1. out 是 CLR 级别的装饰 ref。所以低级代码可以将其视为等同于 ref.

    void Main()
    {
        Ref r = R;
        Out o = (Out)Delegate.CreateDelegate(typeof(Out), null, r.Method);
        int i = 2;
        o(out i);
        i.Dump();
    }
    
    delegate void Out(out int x);
    delegate void Ref(ref int x);
    
    void R(ref int x)
    {
        x++;
    }
    
  2. Setup 接受委托,然后在闭包对象上使用私有反射。

  3. Setup 采用 Expression<T>,即 lambda 的语法树并以非标准方式解释表达式。

    在此上下文中,lambda 表达式不是旨在执行的 C# 代码,而是本质上描述如何设置模拟的 DSL

选项 3 似乎最有可能