Подтвердить что ты не робот

Принудительное узкое неявное принуждение во время компиляции

Я пытаюсь определить структуру, которая использует переменную с ограниченным диапазоном чисел и неявное принуждение от ints. Я хотел бы иметь возможность принудительно строить ошибки сборки, если с этой структурой используются константы или другие жестко заданные значения.

Вот пример того, что я пытаюсь выполнить.

    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 выше.

Я нашел нечто подобное для другого языка, но не для С#.

4b9b3361

Ответ 1

Я думаю, вы можете сделать это, используя пользовательские атрибуты и анализы кода roslyn. Позвольте мне набросать решение. Это должно, по крайней мере, решить первую проблему, когда вы инициализируетесь литералом.

Сначала вам понадобится настраиваемый атрибут, который применяется к вашей структуре, чтобы анализы кода могли знать допустимый диапазон:

[AttributeUsage(System.AttributeTargets.Struct)]
public class MinMaxSizeAttribute : Attribute
{
    public int MinVal { get; set;}
    public int MaxVal { get; set;}
    public MinMaxSizeAttribute()
    {
    }
}

Что вы здесь делаете, вы сохраняете значение min и max в атрибуте. Таким образом, вы можете использовать это позже в анализах исходного кода.

Теперь примените этот атрибут к объявлению структуры:

[MinMaxSize(MinVal = 0, MaxVal = 100)]
public struct Foo
{
    //members and implicit conversion operators go here
}

Теперь информация типа для struct 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, и если он проверяет, является ли правая сторона литералом. Когда он является литералом, он пытается получить целочисленное значение и сравнивает его с MinVal и MaxVal, связанными с типом. Если значения превышают этот диапазон, он сообщит об ошибке диагностики.

Обратите внимание, что весь этот код в основном не проверен. Он компилирует и передает некоторые базовые тесты. Но он предназначен только для иллюстрации возможного решения. Для получения дополнительной информации см. Rsolyn Docs

Второй случай, который вы хотите использовать, более сложный, потому что вам нужно будет применить анализ потока данных, чтобы получить значение x.