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

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

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

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

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


person user3657661    schedule 30.09.2015    source источник
comment
это невозможно. byte - это тип с диапазоном 255. Я не думаю, что вы можете ограничить это во время компиляции или создать собственный тип.   -  person M.kazem Akhgary    schedule 30.09.2015
comment
@M.kazemAkhgary Это возможно, изменив Roslyn, хотя я не уверен, насколько это сложно или разумно.   -  person Dmytro Shevchenko    schedule 30.09.2015
comment
Интересный вопрос! В Visual Studio 2013, если я ввожу слишком большое буквальное значение, Intellisense узнает об этом. Интересно, есть ли способ определить класс с аналогичной поддержкой Intellisense или он встроен.   -  person Doug Dawson    schedule 30.09.2015
comment
Вы могли бы использовать собственный Enum, хотя это было бы болью в заднице :)   -  person Derek    schedule 30.09.2015
comment
@Derek, к сожалению, enum принимает любое значение в диапазоне int.   -  person M.kazem Akhgary    schedule 30.09.2015
comment
Почему бы не проанализировать входные значения в конструкторе, учитывая необходимое количество условий (значение > 0 && значение ‹ 150 и т. д.) и генерировать исключение, если они не выполняются?   -  person varocarbas    schedule 30.09.2015
comment
@varocarbas это будет ошибка времени выполнения. И происходит, когда код выполняется. OP хочет проверить время компиляции и исключение перед выполнением кода.   -  person M.kazem Akhgary    schedule 30.09.2015
comment
@M.kazemAkhgary Да, я знаю. Но мне интересно, почему. В чем проблема?   -  person varocarbas    schedule 30.09.2015
comment
Вы. Я согласен. Может в коллективе пригодится. Не позволять другим ошибаться. Однако это можно сделать с помощью пользовательских предупреждений, используя директивы #. Также resharper поддерживает настраиваемые сообщения об ошибках. Но я не уверен, можно ли генерировать ошибку для диапазонов номеров. Также вы можете написать некоторую документацию по структуре, используя /// @varocarbas   -  person M.kazem Akhgary    schedule 30.09.2015
comment
Я провел кучу исследований и считаю, что это возможно с помощью плагина Visual Studio, который вмешивается в директивы компилятора. В конечном счете, это слишком много усилий, когда я могу просто зажать число или создать исключение во время выполнения. Я вижу, что Microsoft позволяет накладывать сужающие ограничения на универсальные типы, т. е. я могу потребовать общий T, где T должен быть чем-то конкретным, но вы не можете сделать это для реальных данных, только для типов. Было бы неплохо, если бы я мог определить неявный оператор с чем-то вроде (int x.Where(x ‹ 100)) Может быть что-то, что стоит запросить.   -  person user3657661    schedule 02.10.2015


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

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

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

person Kolja    schedule 14.11.2015