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

Posted

技术标签:

【中文标题】在编译时强制进行狭窄的隐式强制【英文标题】:Force a narrow implicit coercion at compile time 【发布时间】:2015-12-28 15:40:08 【问题描述】:

我正在尝试定义一个结构,该结构使用具有有限数字范围的变量,以及来自整数的隐式强制。如果此结构使用任何常量或其他硬编码值,我希望能够强制构建错误。

这是我想要完成的一个示例。

    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 一样。

我找到了something similar for a different language,但不是 C#。

【问题讨论】:

不可能。 byte 是一种范围为 255 的类型。我认为您不能在编译时限制它或创建自定义类型。 @M.kazemAkhgary 可以通过修改 Roslyn 来实现,但我不确定这会有多困难或合理 有趣的问题!在 Visual Studio 2013 中,如果我输入的文字值太大,Intellisense 会知道。我想知道是否有一种方法可以定义具有类似 Intellisense 支持的类,或者是否已经加入。 @M.kazemAkhgary 是的,我知道。但我想知道为什么。那会有什么问题? 我已经做了很多研究,我相信这可能使用一个混杂编译器指令的 Visual Studio 插件来实现。最终,当我只能限制数字或抛出运行时异常时,这将是太多的努力。我看到微软允许你对泛型类型施加更窄的约束,即我可以要求泛型 T ,其中 T 必须是特定的,但你不能对实际数据执行此操作,只是类型。如果我可以用 (int x.Where(x 【参考方案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);

对于变量声明,您需要注册一个不同的SyntaxKind,但为简单起见,我将在这里坚持一个。

让我们看看分析逻辑:

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 的值。

【讨论】:

以上是关于在编译时强制进行狭窄的隐式强制的主要内容,如果未能解决你的问题,请参考以下文章

强/若类型语言 动/静态语言

使用接口的隐式运算符

怎样让vs2013不进行强制类型转换

带你玩转JavaScript中的隐式强制类型转换

接口上的 C# 泛型隐式强制转换失败

Java除了数字类型的自动隐式类型转换,对类有类似功能么?