FAQ C#Consultez toutes les FAQ
Nombre d'auteurs : 41, nombre de questions : 274, dernière mise à jour : 27 mai 2018 Ajouter une question
Cette FAQ a été réalisée pour répondre aux questions les plus fréquemment posées concernant C# sur le forum Développement DotNET
Je tiens à souligner qu'elle ne garantit en aucun cas que les informations qu'elle contient sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Si vous trouvez une erreur, ou que vous souhaitez devenir rédacteur, lisez ceci .
Sur ce, je vous souhaite une bonne lecture.
Commentez cette FAQ : Commentez
- Qu'est-ce qu'un type primitif ?
- Quelles sont les visibilités de classe en C# ?
- Quelles sont les visibilités des membres de classe en C# ?
- Qu'est-ce qu'une propriété ?
- Comment créer une propriété ?
- Comment déclarer un champ en tant que constante ?
- Quelle est la différence entre les champs const et readonly ?
- Comment appeler un constructeur à partir d'un autre constructeur de la même classe ?
- Comment appeler un constructeur de la classe de base ?
- Qu'est-ce qu'une classe partielle ?
- Comment déclarer une variable de manière globale ?
- Comment passer un nombre variable d'arguments à une fonction ?
- Comment passer un nombre variable d'arguments à une fonction avec des types différents ?
- Qu'est-ce qu'un "générique" ?
- Comment spécifier des contraintes sur des classes ou méthodes génériques ?
- Comment tester si on a affaire à un type valeur ou référence dans une classe générique ?
- Comment empêcher une classe d'être dérivée ?
- Comment faire pour que sa classe soit énumérable avec foreach ?
- Comment créer un itérateur ?
- Quelles sont les conventions de nommage en C# ?
- Comment déclarer ses propres évènements ?
Les types primitifs sont les "briques de base" du langage : ce sont des types de données simples, qui ne possèdent aucune donnée membre mais représentent directement une donnée. Tous ces types ont un alias en C# qui correspond à un type du .NET Framework.
Voici la liste complète des types primitifs :
Alias C# | Type .NET | Description |
bool | System.Boolean | Représente une valeur vraie (true) ou fausse (false) sur 8 bits |
char | System.Char | Représente un caractère Unicode sur 16 bits. Une séquence de caractères constitue une chaine (string) |
byte | System.Byte | Représente un entier non signé sur 8 bits (octet), dont la valeur est comprise entre 0 et 255 |
sbyte | System.SByte | Représente un entier signé sur 8 bits, dont la valeur est comprise entre -128 et 127 |
short | System.Int16 | Représente un entier signé sur 64 bits, dont la valeur est comprise entre -32768 et 32767 |
ushort | System.UInt16 | Représente un entier non signé sur 64 bits, dont la valeur est comprise entre 0 et 65535 |
int | System.Int32 | Représente un entier signé sur 32 bits, dont la valeur est comprise entre -2147483648 et 2147483647 |
uint | System.UInt32 | Représente un entier non signé sur 32 bits, dont la valeur est comprise entre 0 et 4294967295 |
long | System.Int64 | Représente un entier signé sur 64 bits, dont la valeur est comprise entre -9223372036854775808 et 9223372036854775807 |
ulong | System.UInt64 | Représente un entier non signé sur 64 bits, dont la valeur est comprise entre 0 et 18446744073709551615 |
decimal | System.Decimal | Représente un nombre décimal sur 128 bits, dont la valeur est comprise entre 7,9E+028 et -7,9E+028 (approximativement). La plage des valeurs possibles est moins grande qu'avec les types flottants, mais la précision est meilleure (28 chiffres significatifs), ce qui rend le type decimal particulièrement adapté pour les calculs financiers |
double | System.Double | Représente un nombre décimal à virgule flottante sur 64 bits (double précision), dont la valeur est comprise entre -1.79E+308 et 1.79E+308. La précision est de 15 à 16 chiffres significatifs. |
float | System.Single | Représente un nombre décimal à virgule flottante sur 32 bits (simple précision), dont la valeur est comprise entre -3.4E+38 et 3.4E+38. La précision est de 7 chiffres significatifs. |
Certains types primitifs numériques ont un suffixe correspondant qui permet d'indiquer le type d'une valeur litérale :
- Aucun suffixe : int ou double (42 ou 42.0)
- U ou u : uint (42U)
- L ou l : long (42L)
- UL ou ul : ulong (42UL)
- D ou d : double (42d)
- F ou f : float (42f)
- M ou m : decimal (42m)
Les différents niveaux de visibilité pour une classe (ou structure, interface, etc.) en C# sont les suivants :
- public : La classe est accessible à partir de n'importe quel code, y compris dans un autre assembly
- internal : La classe n'est accessible que dans l'assembly ou elle est définie
- protected : La classe n'est accessible qu'à partir des classes qui héritent de celle qui la contient (applicable uniquement aux classes imbriquées)
- protected internal : La classe n'est accessible qu'à partir des classes qui héritent de celle qui la contient, et à partir du même assembly (applicable uniquement aux classes imbriquées)
- private : La classe n'est accessible qu'a partir de la classe qui la contient (applicable uniquement aux classes imbriquées)
Si ce n'est pas précisé, une classe est par défaut :
- interne (internal) si elle est déclarée directement dans un namespace
- privée (private) si elle est déclarée dans une autre classe (classe imbriquée)
Les différents niveaux de visibilité pour les membres d'une classe (ou structure) en C# sont les suivants :
- public : Le membre est accessible à partir de n'importe quel code, y compris dans un autre assembly
- internal : Le membre n'est accessible qu'à partir de l'assembly courant
- protected : Le membre n'est accessible qu'à partir des classes dérivées
- protected internal : Le membre n'est accessible qu'à partir des classes dérivées, et à partir de l'assembly courant
- private : Le membre n'est accessible qu'a partir de la classe qui le contient
Si ce n'est pas précisé, un membre d'une classe ou d'une structure est privé par défaut. Dans une interface ou un enum, les membres sont implicitement publics (il est d'ailleurs illégal de spécifier leur niveau d'accessibilité).
Une propriété est un membre d'une classe (ou structure, ou interface) qui s'utilise comme un champ, mais peut implémenter sa propre logique pour accéder à la valeur. Une propriété se compose généralement de 2 accesseurs, get (pour récupérer la valeur) et set (pour la modifier).
Une propriété peut être en lecture/écriture (accesseurs get et set), en lecture seule (seulement un accesseur get), ou, plus rarement, en écriture seule (seulement un accesseur set).
Notez que selon les bonnes pratiques de Microsoft, une propriété ne doit pas effectuer de traitement lourd, et ne doit pas renvoyer un tableau. Dans ces cas-là, on utilise plutôt une méthode
Une propriété est définie ainsi :
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class A { private string _maVariable; public string MaVariable { get { return _maVariable; } set { _maVariable = value; } } } |
Pour un accès en lecture seule, il suffit de supprimer le set{ } et inversement pour un accès en écriture seule.
Notez que l'accesseur set prend implicitement un paramètre nommé value, du même type que la propriété.
En C# 3.0, il existe un "raccourci" pour déclarer une propriété qui n'implémente pas de logique particulière dans ses accesseurs. C'est ce qu'on appelle une propriété « auto-implémentée » :
Code c# : | Sélectionner tout |
public string MaVariable { get; set; }
Code c# : | Sélectionner tout |
public string MaVariable { get; private set; }
Il suffit d'utiliser const devant la déclaration du champ. L'initialisation devra se faire en même temps que la déclaration car il ne sera plus possible de modifier la valeur de la variable par la suite :
Code c# : | Sélectionner tout |
const int maConstante = 2007;
Bien que similaires, les constantes et les champs readonly sont deux choses bien différentes :
- Une constante (const) permet de définir une valeur qui sera toujours la même, partout dans le programme et à chaque exécution
Si vous faites référence à cette constante dans le code, le compilateur remplacera cette référence par la valeur de la constante.
Notez que le type d'une constante ne peut être qu'un type primitif (int, bool, ulong…) ou de type string. De plus, une constante est toujours statique (c'est implicite, donc inutile de le préciser) - Un champ en lecture seule (readonly) est initialisé lors de la création de l'objet (ou du type si le champ est statique), et ne change plus par la suite
On ne peut initialiser ce champ que dans le constructeur ou immédiatement lors de sa déclaration. Contrairement à une constante, un champ readonly peut être de n'importe quel type, et peut être un membre d'instance ou un membre statique.
Un champ readonly n'a pas forcément la même valeur d'un objet à l'autre, ce n'est pas une constante. Il est simplement constant après avoir été initialisé, mais on peut l'initialiser avec différentes valeurs. |
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | public class MaClasse { public const int MaConstante = 42; public readonly string MonReadOnly; public MaClasse(string monParam) { // initialisation dans le constructeur MonReadOnly = monParam; } } |
Il suffit d'utiliser this dans le header du constructeur (c'est-à-dire juste après la liste des paramètres) :
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | class Employee { private String PrenomNom; public Employee(String param_PrenomNom) { PrenomNom = param_PrenomNom; } public Employee(String param_Prenom, String param_Nom) : this(param_Prenom + " " + param_Nom) { } } |
Il suffit d'utiliser base dans le header du constructeur (c'est-à-dire juste après la liste des paramètres) :
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 | class Employee : Person { public Employee(String param_PrenomNom) : base(param_PrenomNom) // passe le paramètre au constructeur de la classe Person { } } |
Une classe partielle est tout simplement une classe dont la définition est séparée dans plusieurs fichiers sources distincts. Exemple :
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //Fichier MaClasse.1.cs public partial class MaClasse { private List<string> infos; } //Fichier MaClasse.2.cs public partial class MaClasse { public MaClasse() { this.infos = new List<string>(); } } |
Afin d'avoir une variable accessible de n'importe quel endroit de son application il faut la déclarer static.
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class Configuration { private static string _connectionString; public static string ConnectionString { get { return _connectionString; } set { _connectionString = value; } } } |
Il suffit de déclarer un paramètre de type tableau, en le précédant du mot réservé params. On accède ensuite aux paramètres via le tableau :
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | static void Main() { MaFonctionVariable("Bonjour"); MaFonctionVariable("Bonjour", "Au revoir"); } static void MaFonctionVariable(params String[] MesParams) { foreach (String courantString in MesParams) Console.WriteLine("Valeur du paramètre : {0}", courantString); } |
Comme indiqué plus haut, on utilise le mot réservé params pour passer un nombre variable d'arguments à une fonction. Pour pouvoir passer des types différents, il suffit de déclarer le paramètre comme étant de type Object[] : étant donné que tous les types dérivent de Object, le tableau pourra contenir des objets de n'importe quel type
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | static void Main() { MaFonctionVariableEnType("Bonjour"); MaFonctionVariableEnType("Bonjour", 2007); MaFonctionVariableEnType("Bonjour", 2007, true); MaFonctionVariableEnType("Bonjour", 2007, true, 10.1); } static void MaFonctionVariableEnType(params Object[] MesParams) { foreach (Object courantObject in MesParams) Console.WriteLine("Valeur du paramètre : {0}", courantObject); } |
Les génériques sont une fonctionnalité apparue en C# 2.0, similaire aux templates de C++ (mais avec des différences notables). Ils permettent de définir des types ou des méthodes sans connaitre à l'avance le type de certains éléments. Cela permet d'avoir des classes ou méthodes qui offrent la même fonctionnalité pour différents types, de données, plutôt que d'avoir à redévelopper la même fonctionnalité pour chaque type.
On déclare un type ou une méthode générique en déclarant les paramètres de type entre < et >. Exemple pour une classe :
Code c# : | Sélectionner tout |
1 2 3 4 | public class MaClasseGenerique<T> { public T Valeur { get; set; } } |
Code c# : | Sélectionner tout |
1 2 3 4 | public T GetValue<T>() { return (T)value; } |
Pour utiliser un type ou une méthode générique, il faut remplacer le paramètre de type par le type réel qu'on souhaite utiliser :
Code c# : | Sélectionner tout |
1 2 3 | MaClasseGenerique<int> x = new MaClasseGenerique<int>(); x.Valeur = 3; x.Valeur = "hello"; // impossible car Valeur est de type int |
Code c# : | Sélectionner tout |
1 2 3 4 | List<string> stringList = new List<string>(); stringList.Add("Salut"); // ok stringList.Add(1); // impossible car c'est une liste d'objets de type String |
Lorsque vous développez une classe ou une méthode générique, il est possible que vous écriviez quelque chose de ce genre :
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 | public class maClasse<K> { protected K paramK; public maClasse() { paramK = new K(); } } |
Lors de la compilation, Visual Studio vous dira qu'il est impossible de créer une instance de K. Cela est dû au fait que vous ne pouvez pas être sûr que le type K (qui sera substitué par un vrai type par la suite) possède un constructeur sans aucun paramètre.
C'est là qu'intervient le principe des contraintes sur les classes génériques. Grâce à where, il est possible de spécifier que le type K doit posséder un constructeur sans paramètre :
Code c# : | Sélectionner tout |
1 2 3 4 | public class maClasse<K> where K : new() { //... } |
Il est également possible de spécifier que le type K doit hériter d'une classe ou implémenter une interface. Cela permet d'accéder aux membres définis dans la classe de base ou l'interface.
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | public class maClasse<K> where K : new(), IDisposable { protected K paramK; public maClasse() { paramK = new K(); paramK.Dispose(); } } |
Un dernier type de contrainte permet de spécifier que le paramètre de type doit être un type valeur (struct) ou un type référence (class) :
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class maClasseReference<K> where K : class { } public class maClasseValeur<K> where K : struct { } // ... maClasseReference<string> ref1 = new maClasseReference<string>(); // OK, string est un type référence maClasseReference<int> ref2 = new maClasseReference<int>(); // impossible, int est un type valeur maClasseValeur<int> val1 = new maClasseValeur<int>(); // OK, int est un type valeur maClasseValeur<string> val2 = new maClasseValeur<string>(); // impossible, string est un type référence |
Il est possible d'utiliser le mot-clé default afin de récupérer la valeur par défaut d'un type. Pour un type référence, nous récupérerons null alors que pour les types valeurs, cela dépend (0 pour int, float, etc.). Partant de ce principe, il suffit de tester la nullité de la valeur par défaut récupérée pour savoir si on a affaire à un type référence ou valeur.
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class MaClasseGenerique<K> { public void TypeK() { if (default(K) != null) Console.WriteLine("K est un type valeur !"); else Console.WriteLine("K est un type référence !"); } } class Program { static void Main(string[] args) { (new MaClasseGenerique<int>()).TypeK(); (new MaClasseGenerique<string>()).TypeK(); } } |
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | public class MaClasseGenerique<K> { public void TypeK() { if (typeof(K).IsValueType) Console.WriteLine("K est un type valeur !"); else Console.WriteLine("K est un type référence !"); } } |
Il suffit de la qualifier du mot réservé sealed.
Code c# : | Sélectionner tout |
1 2 3 4 | sealed class ClassImpossibleAHeriter { // ... } |
L'instruction foreach permet de parcourir facilement n'importe quelle collection d'objets. Pour rendre une classe énumérable avec foreach, il suffit d'implémenter l'interface IEnumerable. Cette interface définit une seule méthode, nommée GetEnumerator, qui renvoie un objet implémentant l'interface IEnumerator.
Voici un exemple d'une classe qui peut être parcourue avec foreach et renvoie une série de nombres :
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | class LostNumbers : IEnumerable { private int[] _numbers = new[] { 4, 8, 15, 16, 23, 42 }; public IEnumerator GetEnumerator() { return new LostNumbersEnumerator(_numbers); } private class LostNumbersEnumerator : IEnumerator { private int _index; private int[] _numbers; public LostNumbersEnumerator(int[] numbers) { _numbers = numbers; _index = -1; } public void Reset() { _index = -1; } public bool MoveNext() { _index++; return _index < _numbers.Length; } public object Current { get { return _numbers[_index]; } } } } |
Code c# : | Sélectionner tout |
1 2 3 4 5 | LostNumbers numbers = new LostNumbers(); foreach (int n in numbers) { Console.WriteLine(n); } |
C# 2.0 introduit des nouveautés importantes en ce qui concerne l'énumération de collections : d'une part, les interfaces génériques IEnumerable<T> et IEnumerator<T> qui permettent d'énumérer une collection de façon fortement typée, et d'autre part les itérateurs qui permettent d'implémenter des énumérateurs beaucoup plus facilement. On crée un bloc itérateur en utilisant le mot-clé yield. Si on reprend l'exemple précédent en utilisant les fonctionnalités de C# 2.0, le code devient :
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class LostNumbers : IEnumerable<int> { private int[] _numbers = new[] { 4, 8, 15, 16, 23, 42 }; public IEnumerator<int> GetEnumerator() { for(int i = 0; i < _numbers.Length) { yield return _numbers[i]; } } // IEnumerable<T> hérite de IEnumerable, on doit donc définir aussi la méthode // non générique GetEnumerator par implémentation explicite de l'interface IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } |
Notez qu'il est aussi possible d'utiliser les itérateurs pour n'importe quelle méthode qui doit renvoyer une séquence d'objets :
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | public IEnumerable<int> GetLostNumbers() { yield return 4; yield return 8; yield return 15; yield return 16; yield return 23; yield return 42; } |
Pour rendre le code clair et facilement lisible, il est important d'appliquer une convention de nommage des identifieurs et de s'y tenir. De plus, lorsqu'on travaille en équipe, appliquer une convention commune permet de comprendre plus facilement le code écrit par d'autres.
Les conventions de nommage généralement recommandées en C# sont les suivantes :
- Classes : Pascal case (première lettre de chaque mot en majuscule) : public class StringBuilder
- Namespaces : Pascal case : namespace System.Text
- Membres publics : Pascal case: public int GetHashCode()
- Paramètres de méthode : Camel case (première lettre en majuscule, puis première lettre de chaque mot en majuscule) : int arrayIndex
Il n'y a pas vraiment de recommandations pour les membres privés et les variables locales, dans la mesure où ils relèvent de l'implémentation et ne sont pas publiquement visibles. Voici cependant l'une des conventions les plus largement utilisées :
- Champs privés : Camel case précédé d'un underscore (_) : private List<string> _list;
- Méthodes et propriétés privées : Pascal case : private void DoSomething()
- Variables locales : Camel case: int currentValue;
Si ces recommandations peuvent sembler contraignantes au premier abord, sachez qu'on s'y fait très vite, et qu'elles vous faciliteront beaucoup la vie à long terme, car il deviendra beaucoup plus facile de différencier les différents types d'identifieur. Et si vous reprenez du code écrit par un d'autre développeur, vous lui serez reconnaissant d'avoir appliqué ces conventions !
Pour créer ses propres évènements en C#, différents « ingrédients » sont nécessaires :
Il faut tout d'abord une classe dérivée de EventArgs qui contiendra les données associées à l'évènement. Par convention, la classe s'appelle <NomDeLEvenement>EventArgs. Si l'évènement n'a pas de données associées, on utilise directement la classe EventArgs. Prenons l'exemple d'un évènement "message reçu" dans une application de messagerie :
Code c# : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class MessageReceivedEventArgs : EventArgs { private readonly string _from; private readonly string _message; public MessageReceivedEventArgs(string from, string message) { _from = from; _message = message; } public string From { get { return _from; } } public string Message { get { return _message; } } } |
On déclare également un delegate qui définit la signature des méthodes qui gèrent l'évènement. Par convention, le delegate s'appelle <NomDeLEvenement>EventHandler, le type de retour est void, le premier paramètre est de type object et correspond à l'objet qui déclenche l'évènement, et le second paramètre est l'objet qui contient les données de l'évènement :
Code c# : | Sélectionner tout |
public delegate void MessageReceivedEventHandler(object sender, MessageReceivedEventArgs e);
On déclare l'évènement proprement dit de la même façon qu'un champ dont le type est celui du delegate défini plus haut, avec le modificateur event :
Code c# : | Sélectionner tout |
public event MessageReceivedEventHandler MessageReceived;
Code c# : | Sélectionner tout |
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
Notez que, contrairement à une pratique répandue, les bonnes pratiques recommandent de ne pas préfixer le nom de l'évènement par On (OnMessageReceived). Ce nom est généralement réservé à un autre usage, comme on va le voir ci-dessous. |
Code c# : | Sélectionner tout |
1 2 3 4 5 6 | protected virtual void OnMessageReceived(string from, string message) { MessageReceivedEventHandler handler = MessageReceived; if (handler != null) handler(this, new MessageReceivedEventArgs(from, message)); } |
Notez que l'on doit toujours s'assurer qu'il y a des gestionnaires abonnés à l'évènement, en vérifiant si le handler n'est pas null. Dans le cas contraire, tenter de déclencher l'évènement provoquerait une NullReferenceException. |
Enfin, pour déclencher l'évènement, il suffit d'appeler la méthode déclarée ci-dessus :
Code c# : | Sélectionner tout |
OnMessageReceived("Joe", "Hello world !");
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.