在编译时强制进行狭窄的隐式转换

12

我正在尝试定义一个使用范围受限的数字变量和从整数进行隐式转换的结构体。我希望能够在此结构体中强制发生构建错误,如果使用任何常量或其他硬编码值。

以下是我想要实现的示例。

    byte a = 123; // Allowed
    byte b = 123123; // Not allowed
    const int x = 123;
    const int y = 123123;
    byte c = x; // Allowed
    byte d = y; // Not allowed

我希望能够限制数字范围在1到99之间,例如,让MyStruct s = 50;可行,而MyStruct s = 150;会像上面的字节b和d一样导致编译时错误。

我在这里找到了类似于不同语言的内容,但没有C#的。


不可能。byte是一个范围为255的类型。我认为您无法在编译时限制它或创建自定义类型。 - M.kazem Akhgary
@M.kazemAkhgary 这可能通过修改Roslyn实现,但我不确定这有多难或合理。 - Dmytro Shevchenko
有趣的问题!在 Visual Studio 2013 中,如果我输入一个太大的文字值,Intellisense 就会知道。我想知道是否有一种方法可以定义一个具有类似 Intellisense 支持的类,或者这是内置的功能。 - Doug Dawson
1
@M.kazemAkhgary 是的,我知道。但是我在想为什么。那会有什么问题吗? - varocarbas
2
我已经做了很多研究,我相信这可能是通过使用一个干扰编译器指令的Visual Studio插件来实现的。但这需要付出太多的努力,当我可以只是限制数字或抛出运行时异常时。我看到Microsoft允许您对泛型类型施加缩小约束,即我可以要求一个特定的T泛型,但您不能对实际数据进行此操作,只能对类型进行操作。如果我可以定义一个类似于(int x.Where(x < 100))的隐式操作符就好了。这可能值得请求。 - user3657661
显示剩余5条评论
1个回答

1
我认为您可以通过使用自定义属性和Roslyn代码分析来实现此目标。让我勾勒一下解决方案。这应该至少解决第一个用例,其中您使用字面量进行初始化。
首先,您需要一个适用于结构体的自定义属性,以便代码分析能够了解有效范围:
[AttributeUsage(System.AttributeTargets.Struct)]
public class MinMaxSizeAttribute : Attribute
{
    public int MinVal { get; set;}
    public int MaxVal { get; set;}
    public MinMaxSizeAttribute()
    {
    }
}

你在这里做的是将最小值和最大值存储在一个属性中。这样,你可以在源代码分析中稍后使用它。现在将此属性应用于结构声明:
[MinMaxSize(MinVal = 0, MaxVal = 100)]
public struct Foo
{
    //members and implicit conversion operators go here
}

现在,结构体Foo的类型信息包含值范围。接下来需要的是一个DiagnosticAnalyzer来分析您的代码。
public class MyAnalyzer : DiagnosticAnalyzer
{
    internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor("CS00042", 
        "Value not allowed here",
        @"Type {0} does not allow Values in this range", 
        "type checker", 
        DiagnosticSeverity.Error,
        isEnabledByDefault: true, description: "Value to big");
    public MyAnalyzer()
    {
    }

    #region implemented abstract members of DiagnosticAnalyzer

    public override void Initialize(AnalysisContext context)
    {
        context.RegisterSyntaxNodeAction(AnalyzeSyntaxTree, SyntaxKind.SimpleAssignmentExpression);
    }

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

    #endregion

    private static void AnalyzeSyntaxTree(SyntaxNodeAnalysisContext context)
    {

    }
}

这是参与代码分析的基本框架。分析器注册以分析赋值操作:
context.RegisterSyntaxNodeAction(AnalyzeSyntaxTree, SyntaxKind.SimpleAssignmentExpression);

对于变量声明,您需要注册不同的,但为了简单起见,在此我将坚持一个。
让我们来看一下分析逻辑:
private static void AnalyzeSyntaxTree(SyntaxNodeAnalysisContext context)
        {
            if (context.Node.IsKind(SyntaxKind.SimpleAssignmentExpression))
            {
                var assign = (AssignmentExpressionSyntax)context.Node;
                var leftType = context.SemanticModel.GetTypeInfo(assign.Left).GetType();
                var attr = leftType.GetCustomAttributes(typeof(MinMaxSizeAttribute), false).OfType<MinMaxSizeAttribute>().FirstOrDefault();
                if (attr != null && assign.Right.IsKind(SyntaxKind.NumericLiteralExpression))
                {
                    var numLitteral = (LiteralExpressionSyntax)assign.Right;
                    var t = numLitteral.Token;
                    if (t.Value.GetType().Equals(typeof(int)))
                    {
                        var intVal = (int)t.Value;
                        if (intVal > attr.MaxVal || intVal < attr.MaxVal)
                        {
                            Diagnostic.Create(Rule, assign.GetLocation(), leftType.Name);
                        }
                    }
                }
            }
        }

分析器的作用是检查左侧类型是否与MinMaxSize相关联,如果是,则检查右侧是否为文字。当它是文字时,它会尝试获取整数值并将其与与该类型相关联的MinValMaxVal进行比较。如果值超出该范围,它将报告诊断错误。
请注意,所有这些代码大多未经测试。它编译并通过了一些基本测试。但它只是为了说明可能的解决方案。有关更多信息,请查看Rsolyn Docs
您要处理的第二种情况更加复杂,因为您需要应用dataflow analyzes来获取x的值。

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