2009-10-27 17 views
23

Quale sarebbe il minimo codice di boilerplate per un'applicazione console entry-point C# che renderebbe un ben educati cittadino?Minimal, buon cittadino, C# applicazione console boilerplate

Quando qualcuno va a creare un progetto Applicazione console utilizzando Visual Studio (fino al 2008, al momento della scrittura), si sono presentati con un testo standard Program.cs che assomiglia a questo:

class Program 
{ 
    static void Main(string[] args) 
    { 
    } 
} 

Ci sono, tuttavia, alcune cose che tutti devono fare per rendere un'applicazione console un minimamente un buon cittadino. Ad esempio, se si verifica un'eccezione, scrivere un messaggio pulito con errore standard (Console.Error) e non con output standard (Console.Out). Allo stesso modo, impostare il codice di errore su un valore diverso da zero in caso di errore, in modo che i processi di chiamata possano rilevare i guasti.

quello che sarebbe il minimo codice di boilerplate per un'applicazione console entry-point C# che renderebbe un ben educati cittadino? Cosa aggiungere o modificare al seguente?

using System; 
using System.Diagnostics; 
using System.Linq; 

static class Program 
{ 
    static void Run(string[] args) 
    { 
     // TODO Replace line-echoing sample with actual application code 

     string line; 
     while ((line = Console.ReadLine()) != null) 
      Console.WriteLine(line); 
    } 

    static int Main(string[] args) 
    { 
     // TODO Use a more robust arguments parser 
     if (args.Any(arg => arg.Equals("/v") || arg.Equals("-v"))) // verbose? 
      Trace.Listeners.Add(new ConsoleTraceListener(true)); 

     try 
     { 
      Run(args); 
      return Environment.ExitCode; 
     } 
     catch (Exception e) 
     { 
      Console.Error.WriteLine(e.Message); 
      Trace.TraceError(e.ToString()); 

      return Environment.ExitCode != 0 
       ? Environment.ExitCode : 100; 
     } 
    } 
} 

Ciò boilerplate raggiunge:

  • Se viene generata un'eccezione:
    • un messaggio pulito viene stampato sullo standard error
    • tutti i dettagli sono tracciate
    • la il codice di uscita è impostato su un valore arbitrario diverso da zero (100), a meno che non sia già impostato
  • registrazione può essere attivata dinamicamente tramite un interruttore dettagliato
  • tracciamento viene inviato errore standard per non interferire con il produzione reale
  • In caso di successo, il codice di uscita riflette l'ultimo valore Environment.ExitCode , che di solito è zero, ma può essere modificata codice valle
  • Program classe è statica

non-obiettivi di questa domanda:

  • Identificare gli argomenti della riga di comando il codice di gestione o libreria
  • identificare un logging esterno o tracciare biblioteca
+1

Una la domanda è? :) Seriamente, avresti dovuto lavorarci un po 'in una domanda. E forse una risposta. –

+3

La domanda è lì in grassetto. –

+0

In grassetto, sì. E 20 righe in basso. –

risposta

3

Per me vorrei vedere Run(args) sostituito con il istanziazione di una classe.Qualcosa di simile:

Prova principale:

try 
{ 
    // Process args to get parameters for AClass 
    AClass program = new AClass(a, b); 
    return program.Run(); 
} 

AClass:

public class AClass { 
    AClass(string a, string b) { ... } 
    public int Run() { 
     ... 
     return Environment.ExitCode; 
    } 
} 

Qualcosa di simile sta per scoraggiare codice procedurale e incoraggiare la creazione di un approccio orientato agli oggetti.

Per quanto riguarda il costruttore AClass(), penso che gli argomenti devono essere trattati prima di essere passato nel AClass() invece di AClass() dover sapere che è stato creato tramite una console app.

7

Penso che tutto dipenda dai requisiti effettivi per quell'app. Se non è necessario eseguire la gestione personalizzata degli errori, non farlo. Se nessun programma deve controllare il codice di uscita dell'app, non è necessario restituirlo; e credo che ci siano casi in cui tali requisiti non si applicano.

In altre parole, il minimo è minimo; fare la cosa più semplice che potrebbe funzionare. E se la tua app soddisfa i requisiti, suppongo che possiamo chiamarla ben educata.

+1

+1 per raccomandare contro la sovrastruttura in termini di robustezza. – dsimcha

0

Io uso emacs e una cosa chiamata defaultcontent.el. Se non hai familiarità, Emacs è un editor di testo ed è molto estensibile. defaultcontent.el è un'estensione che inserisce (sorpresa) il contenuto predefinito in nuovi file, quando vengono creati per la prima volta da emacs.

Quindi, se provo ad aprire un file .cs che non esiste, emacs lo crea, quindi inserisce una serie di contenuti predefiniti in esso. Decido quali contenuti includere nei nuovi file. Per i miei file C#, questo contenuto include:

  • documentazione semplice con timestamp, copyright, altra scheda tecnica.
  • un gruppo di montaggio-scope attributi (FileVersion, AssemblyDescription, ecc)
  • una singola classe
  • un contructor che accetta una matrice di argomenti stringa, e li analizza; la logica parse è un'istruzione switch, con alcune regole per analizzare interi, stringhe e booleani.
  • a Metodo di utilizzo, che stampa le informazioni di utilizzo.
  • un metodo Main, che crea un'istanza della classe e chiama "Run()"; questo è circondato da un try..catch e nel catch, viene chiamato il metodo Usage(). Se si verifica un errore in qualsiasi punto del programma, viene visualizzato il messaggio di utilizzo.

Il defaultcontent.el consente inoltre di posizionare il cursore dove voglio. Nel mio caso, questo è nel mezzo del metodo Run() vuoto.

Questo è il mio contenuto predefinito:

// default.cs 
// ------------------------------------------------------------------ 
// 
// Description goes here.... 
// 
// Author: MyUser 
// built on host: MyMachine 
// Created Tue Oct 27 15:01:18 2009 
// 
// last saved: 
// Time-stamp: <2009-October-20 00:18:52> 
// ------------------------------------------------------------------ 
// 
// Copyright Notice here 
// All rights reserved! 
// 
// ------------------------------------------------------------------ 

using System; 
using System.Reflection; 


// to allow fast ngen 
[assembly: AssemblyTitle("default.cs")] 
[assembly: AssemblyDescription("insert purpose here")] 
[assembly: AssemblyConfiguration("")] 
[assembly: AssemblyCompany("Dino Chiesa")] 
[assembly: AssemblyProduct("Tools")] 
[assembly: AssemblyCopyright("Copyright notice again")] 
[assembly: AssemblyTrademark("")] 
[assembly: AssemblyCulture("")] 
[assembly: AssemblyVersion("1.1.1.1")] 


namespace Cheeso.ToolsAndTests 
{ 

    public class default 
    { 
    // ctor 
    public default() {} 

    string _positionalParam1; 
    string _param1; 
    int _intParam = DefaultIntParamValue; 
    bool _flag1; 

    private readonly int DefaultIntParamValue = -99; 

    // ctor 
    public default (string[] args) 
    { 
     for (int i=0; i < args.Length; i++) 
     { 
      switch (args[i]) 
      { 
      case "-stringArg": 
       i++; 
       if (args.Length <= i) throw new ArgumentException(args[i]); 
       _param1 = args[i]; 
       break; 

      case "-intArg": 
       i++; 
       if (args.Length <= i) throw new ArgumentException(args[i]); 
       if (_intParam != DefaultIntParamValue) 
        throw new ArgumentException(args[i]); 
       if (args[i].StartsWith("0x")) 
        _intParam = System.Int32.Parse(args[i].Substring(2), System.Globalization.NumberStyles.AllowHexSpecifier); 
       else 
        _intParam = System.Int32.Parse(args[i]); 
       break; 


      case "-boolarg": 
       _flag1 = true; 
       break; 

      case "-?": 
       throw new ArgumentException(args[i]); 

      default: 
       if (_positionalParam1 != null) 
        throw new ArgumentException(args[i]); 

       _positionalParam1 = args[i]; 
       break; 
      } 
     } 

     // default values 
     if (_positionalParam1 == null) 
      _positionalParam1 = "default.value.for.positional.param"; 

     if (_param1 == null) 
      _param1 = "default.value.for.param1"; 

     if (_param2 == 0) 
      _param2 = DEFAULT_value_for_param2; 

    } 

    public void Run() 
    { 



    } 

    public static void Usage() 
    { 
     Console.WriteLine("\ndefault: <usage statement here>.\n"); 
     Console.WriteLine("Usage:\n default [-arg1 <value>] [-arg2]"); 
    } 


    public static void Main(string[] args) 
    { 
     try 
     { 
     new default(args) 
      .Run(); 
     } 
     catch (System.Exception exc1) 
     { 
     Console.WriteLine("Exception: {0}", exc1.ToString()); 
     Usage(); 
     } 
    } 

    } 
} 

Ho anche defaultcontent allestito per .c, cpp, .vb, Csproj, .xml, XSD, .wsdl, makefile, e molti altri tipi di file.

2

Su questo argomento, ho trovato questo articolo per essere il più in profondità e informativi:

appplications Console in.NET, o l'insegnamento di un nuovo cane vecchi trucchi

Michael Brook

MSDN Magazine Feb 2004

http://msdn.microsoft.com/en-us/magazine/cc164014.aspx

Problemi correlati