2015-09-30 15 views
12

Sto provando a definire una struttura che utilizza una variabile con un intervallo limitato di numeri e una coercizione implicita da ints. Mi piacerebbe essere in grado di forzare gli errori di compilazione se si utilizzano costanti o altri valori codificati con questa struttura.Forza una stretta coercizione implicita al momento della compilazione

Ecco un esempio di ciò che sto cercando di realizzare.

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 

Vorrei idealmente piace essere in grado di, per esempio, limitare un numero da 1 a 99, in modo che MyStruct s = 50; funziona ma MyStruct s = 150; causa un errore di compilazione come i byte b e d sopra fare.

Ho trovato something similar for a different language, ma non per C#.

+0

non è possibile. byte è un tipo con un intervallo di 255. Non penso che sia possibile limitarlo in fase di compilazione o per creare un tipo personalizzato. –

+0

@M.kazemAkhgary Potrebbe essere possibile modificando Roslyn, anche se non sono sicuro di quanto sia difficile o ragionevole quello che sarebbe –

+0

Interessante domanda! In Visual Studio 2013, se inserisco un valore letterale troppo grande, l'Intellisense lo sa. Mi chiedo se c'è un modo per definire una classe con supporto Intellisense simile o se è in cottura. –

risposta

0

Penso che si possa fare ciò usando gli attributi personalizzati e le analisi del codice di Roslyn. Lasciami abbozzare una soluzione. Questo dovrebbe almeno risolvere il primo caso in cui si inizializza con un valore letterale.

In primo luogo si avrebbe bisogno di un attributo personalizzato che si applica al tuo struct per permettere al codice di analisi per essere in grado di conoscere la gamma valida:

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

Quello che fai qui è di memorizzare il minimo e il valore massimo in un attributo. In questo modo puoi usarlo più avanti nelle analisi del codice sorgente.

Ora applicare questo attributo per la dichiarazione struct:

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

Ora le informazioni sul tipo per la struct Foo contiene il campo di valori. La prossima cosa di cui hai bisogno è una DiagnosticAnalyzer per analizzare il tuo codice.

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) 
    { 

    } 
} 

Questo è lo scheletro osseo per partecipare alle analisi del codice. L'analizzatore registra per analizzare le assegnazioni:

context.RegisterSyntaxNodeAction(AnalyzeSyntaxTree, SyntaxKind.SimpleAssignmentExpression); 

Per dichiarazioni di variabili si avrebbe bisogno di registrarsi per un diverso SyntaxKind ma per semplicità mi limiterò a uno qui.

Lascia per avere uno sguardo alla logica analisi:

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); 
         } 
        } 
       } 
      } 
     } 

Che l'analizzatore non è, sta controllando se il tipo sul lato sinistro ha un MinMaxSize ad esso associato e, se così si controlla se il lato destro è un letterale. Quando si tratta di un valore letterale, tenta di ottenere il valore intero e lo confronta con lo MinVal e lo MaxVal associato al tipo. Se i valori superano tale intervallo, verrà segnalato un errore di diagnostica.

Si prega di notare che tutto questo codice è per lo più non testato. Compila e ha passato alcuni test di base. Ma ha il solo scopo di illustrare una possibile soluzione. Per ulteriori informazioni, consultare Rsolyn Docs

Il secondo caso che si desidera coprire è più complesso in quanto è necessario applicare dataflow analyzes per ottenere il valore di x.

Problemi correlati