using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Developpez.Dotnet.Properties; using Developpez.Dotnet.Text; using JetBrains.Annotations; using CH = Developpez.Dotnet.CompatibilityHelper; namespace Developpez.Dotnet { /// /// Fournit des méthodes d'extensions à usage générique /// public static class CoreExtensions { #region Null handling /// /// Renvoie la valeur courante si elle est non nulle. Sinon, renvoie la valeur par défaut spécifiée. /// Equivalent à l'opérateur C# ?? /// /// Type de la valeur courante /// Valeur courante /// Valeur par défaut à renvoyer si la valeur courante est nulle /// La valeur courante si elle est non nulle. Sinon, la valeur par défaut public static T Default(this T obj, T defaultValue) where T : class { return obj ?? defaultValue; } /// /// Si la valeur courante n'est pas nulle, renvoie la valeur spécifiée par le sélecteur. Sinon, /// renvoie la valeur par défaut spécifiée. /// /// Type de la valeur courante /// Type de retour du sélecteur /// Valeur courante /// Sélecteur indiquant la valeur à renvoyer /// Valeur par défaut à renvoyer si la valeur courante est nulle /// Si la valeur courante n'est pas nulle, la valeur spécifiée par le sélecteur. Sinon, la valeur par défaut public static TResult IfNotNull( this T obj, [NotNull] Func selector, TResult valueIfNull) where T : class { selector.CheckArgumentNull("selector"); if (obj == null) return valueIfNull; return selector(obj); } /// /// Si la valeur courante n'est pas nulle, renvoie la valeur spécifiée par le sélecteur. Sinon, /// renvoie la valeur par défaut du type de retour spécifié. /// /// Type de la valeur courante /// Type de retour du sélecteur /// Valeur courante /// Sélecteur indiquant la valeur à renvoyer /// Si la valeur courante n'est pas nulle, la valeur spécifiée par le sélecteur. Sinon, la valeur par défaut du type de retour spécifié public static TResult IfNotNull( this T obj, [NotNull] Func selector) where T : class { return obj.IfNotNull(selector, default(TResult)); } /// /// Teste l'égalité de 2 objets, sans risque de NullReferenceException si l'un des objets /// est null. /// /// Type des objets à comparer /// Premier objet à comparer /// Second objet à comparer /// true si les objets sont égaux ou tous les 2 null, sinon false. public static bool SafeEquals(this T obj, T other) { return EqualityComparer.Default.Equals(obj, other); } #endregion #region Between, StrictlyBetween /// /// Teste si une valeur est entre les 2 valeurs spécifiées (bornes inclues) /// /// Type de la valeur /// Valeur à tester /// Borne inférieure /// Borne supérieure /// true si min <= value <= max, sinon false public static bool Between( this T value, T min, T max) where T : IComparable { var comparer = Comparer.Default; int compMin = comparer.Compare(min, value); int compMax = comparer.Compare(value, max); return compMin <= 0 && compMax <= 0; } /// /// Teste si une valeur est strictement entre les bornes spécifiées (bornes non inclues) /// /// Type de la valeur /// Valeur à tester /// Borne inférieure /// Borne supérieure /// true si min < value < max, sinon false public static bool StrictlyBetween(this T value, T min, T max) where T : IComparable { var comparer = Comparer.Default; int compMin = comparer.Compare(min, value); int compMax = comparer.Compare(value, max); return compMin < 0 && compMax < 0; } #endregion #region In /// /// Teste si une valeur appartient à la liste spécifiée /// /// Type de la valeur /// Valeur à tester /// Liste à laquelle la valeur doit appartenir /// true si la valeur appartient à la liste, sinon false public static bool In( this T value, [NotNull] params T[] list) { list.CheckArgumentNull("list"); return list.Contains(value); } /// /// Teste si une valeur appartient à la liste spécifiée /// /// Type de la valeur /// Valeur à tester /// Liste à laquelle la valeur doit appartenir /// true si la valeur appartient à la liste, sinon false public static bool In( this T value, [NotNull] IEnumerable list) { list.CheckArgumentNull("list"); return list.Contains(value); } #endregion #region Argument checking /// /// Vérifie si un argument est null, et lève une ArgumentNullException /// si c'est le cas. /// /// Type du paramètre /// Valeur de l'argument /// Nom du paramètre [AssertionMethod] public static void CheckArgumentNull( [AssertionCondition(AssertionConditionType.IS_NOT_NULL), NoEnumeration] this T value, [InvokerParameterName] string paramName) where T : class { if (value == null) throw new ArgumentNullException(paramName); } /// /// Vérifie si un argument est null, et lève une ArgumentNullException /// avec le message spécifié si c'est le cas. /// /// Type du paramètre /// Valeur de l'argument /// Nom du paramètre /// Message de l'exception [AssertionMethod] public static void CheckArgumentNull( [AssertionCondition(AssertionConditionType.IS_NOT_NULL), NoEnumeration] this T value, [InvokerParameterName] string paramName, string message) where T : class { if (value == null) throw new ArgumentNullException(paramName, message); } /// /// Vérifie qu'un argument est dans la plage de valeurs spécifiées, et lève une /// ArgumentOutOfRangeException si ce n'est pas le cas /// /// Type du paramètre /// Valeur de l'argument /// Nom du paramètre /// Valeur minimale de l'argument /// Valeur maximale de l'argument public static void CheckArgumentOutOfRange( this T value, [InvokerParameterName] string paramName, T min, T max) where T : IComparable { if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0) throw new ArgumentOutOfRangeException(paramName); } /// /// Vérifie qu'un argument est dans la plage de valeurs spécifiées, et lève une /// ArgumentOutOfRangeException avec le message spécifié si ce n'est pas le cas /// /// Type du paramètre /// Valeur de l'argument /// Nom du paramètre /// Valeur minimale de l'argument /// Valeur maximale de l'argument /// Message de l'exception public static void CheckArgumentOutOfRange( this T value, [InvokerParameterName] string paramName, T min, T max, string message) where T : IComparable { if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0) throw new ArgumentOutOfRangeException(paramName, message); } /// /// Vérifie qu'un argument est une valeur d'enum valide, et lève une /// ArgumentOutOfRangeException si ce n'est pas le cas. /// /// Valeur de l'argument /// Nom du paramètre public static void CheckArgumentInEnum( this Enum value, [InvokerParameterName] string paramName) { if (!Enum.IsDefined(value.GetType(), value)) throw new ArgumentOutOfRangeException(paramName); } /// /// Vérifie qu'un argument est une valeur d'enum valide, et lève une /// ArgumentOutOfRangeException avec le message spécifié si ce n'est pas le cas. /// /// Valeur de l'argument /// Nom du paramètre /// Message de l'exception public static void CheckArgumentInEnum( this Enum value, [InvokerParameterName] string paramName, string message) { if (!Enum.IsDefined(value.GetType(), value)) throw new ArgumentOutOfRangeException(paramName, message); } #endregion #region Switch /// /// Commence un bloc Switch sous forme de méthode d'extension. Le bloc renverra /// la valeur correspondant au premier cas évalué à true, ou à défault, la valeur /// par défaut spécifiée. /// /// Type de la valeur à tester /// Type de la valeur de retour /// Valeur à tester /// Valeur de retour par défaut /// la valeur correspondant au premier cas évalué à true, ou à défault, la valeur par défaut spécifiée public static SwitchContext Switch(this T testValue, TResult defaultResult) { return new SwitchContext(testValue, defaultResult); } /// /// Commence un bloc Switch sous forme de méthode d'extension. Le bloc renverra /// la valeur correspondant au premier cas évalué à true, ou à défault, la valeur /// par défaut du type de retour. /// /// Type de la valeur à tester /// Type de la valeur de retour /// Valeur à tester /// la valeur correspondant au premier cas évalué à true, ou à défault, la valeur par défaut du type de retour public static SwitchContext Switch(this T testValue) { return new SwitchContext(testValue, default(TResult)); } /// /// Représente un bloc Switch en cours d'évaluation /// /// Type de la valeur à tester /// Type de la valeur de retour public class SwitchContext { private readonly T _testValue; private TResult _result; private bool _matchFound; internal SwitchContext(T testValue, TResult defaultResult) { this._testValue = testValue; this._result = defaultResult; } /// /// Ajoute un nouveau cas évalué à true si la valeur à tester est égale à la valeur spécifiée. /// La valeur à renvoyer est spécifiée sous forme d'une valeur fixe. /// /// La valeur à laquelle comparer la valeur à tester /// La valeur à renvoyer si ce cas est évalué à true /// Le bloc Switch courant public SwitchContext Case(T caseValue, TResult caseResult) { return Case(v => caseValue.SafeEquals(v), v => caseResult); } /// /// Ajoute un nouveau cas évalué à true si la valeur à tester est égale à la valeur spécifiée. /// La valeur à renvoyer est évaluée par la fonction spécifiée /// /// La valeur à laquelle comparer la valeur à tester /// La fonction qui évalue le résultat si ce cas est évalué à true /// Le bloc Switch courant public SwitchContext Case( T caseValue, [NotNull] Func caseEvaluator) { caseEvaluator.CheckArgumentNull("caseEvaluator"); return Case(v => caseValue.SafeEquals(v), caseEvaluator); } /// /// Ajoute un nouveau cas évalué à true si le prédicat spécifié est évalué à true. /// La valeur à renvoyer est spécifiée sous forme d'une valeur fixe. /// /// Le prédicat à évaluer par rapport à la valeur à tester /// La valeur à renvoyer si ce cas est évalué à true /// Le bloc Switch courant public SwitchContext Case( [NotNull] Func casePredicate, TResult caseResult) { casePredicate.CheckArgumentNull("casePredicate"); return Case(casePredicate, v => caseResult); } /// /// Ajoute un nouveau cas évalué à true si le prédicat spécifié est évalué à true. /// La valeur à renvoyer est évaluée par la fonction spécifiée /// /// Le prédicat à évaluer par rapport à la valeur à tester /// La fonction qui évalue le résultat si ce cas est évalué à true /// Le bloc Switch courant public SwitchContext Case( [NotNull] Func casePredicate, [NotNull] Func caseEvaluator) { casePredicate.CheckArgumentNull("casePredicate"); caseEvaluator.CheckArgumentNull("caseEvaluator"); if (_matchFound) return this; if (casePredicate(_testValue)) { _result = caseEvaluator(_testValue); _matchFound = true; } return this; } /// /// Spécifie la valeur par défaut à renvoyer si tous les cas précédents ont été évalués à false. /// Cette méthode doit être la dernière appelée sur le bloc Switch, faute de quoi les cas suivants /// ne seront pas évalués. /// /// Valeur par défaut à renvoyer /// Le bloc Switch courant public SwitchContext Else(TResult defaultResult) { return Else(v => defaultResult); } /// /// Spécifie la fonction qui évaluera le résultat par défaut si tous les cas précédents ont été évalués à false. /// Cette méthode doit être la dernière appelée sur le bloc Switch, faute de quoi les cas suivants /// ne seront pas évalués. /// /// Fonction qui évalue le résultat par défaut /// Le bloc Switch courant public SwitchContext Else([NotNull] Func defaultEvaluator) { defaultEvaluator.CheckArgumentNull("defaultEvaluator"); if (_matchFound) return this; _result = defaultEvaluator(_testValue); _matchFound = true; return this; } /// /// Lève une exception si aucun cas correspondant n'a été trouvé /// /// Le bloc Switch courant public SwitchContext ElseThrow() { if (_matchFound) return this; throw new NoMatchFoundException(); } /// /// Lève une exception avec le message spécifié si aucun cas correspondant n'a été trouvé /// /// Message à utiliser pour l'exception /// Le bloc Switch courant public SwitchContext ElseThrow(string message) { if (_matchFound) return this; throw new NoMatchFoundException(message); } /// /// Renvoie le résultat du bloc Switch /// public TResult Result { get { return _result; } } /// /// Renvoie le résultat du block Switch /// /// Le bloc Switch à convertir /// Le résultat du bloc Switch public static implicit operator TResult([NotNull] SwitchContext context) { context.CheckArgumentNull("context"); return context._result; } /// /// Cette exception est levée quand aucun cas correspondant à la valeur testée n'a été trouvé dans le bloc Switch /// #if !SILVERLIGHT && !NETFX_CORE [Serializable] #endif public class NoMatchFoundException : Exception { /// /// Initialise une nouvelle instance de NoMatchFoundException avec le message par défaut /// public NoMatchFoundException() : base(ExceptionMessages.NoMatchFound) { } /// /// Initialise une nouvelle instance de NoMatchFoundException avec le message spécifié /// /// Message de l'exception public NoMatchFoundException(string message) : base(message) { } /// /// Initialise une nouvelle instance de NoMatchFoundException avec le message spécifié et l'exception interne spécifiée /// /// Message de l'exception /// Exception interne public NoMatchFoundException(string message, Exception inner) : base(message, inner) { } #if !SILVERLIGHT && !NETFX_CORE /// /// Initialise une nouvelle instance de NoMatchFoundException à partir de données sérialisées /// /// SerializationInfo qui contient les données d'objet sérialisées relatives à l'exception levée. /// StreamingContext qui contient des informations contextuelles sur la source ou la destination. protected NoMatchFoundException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } #endif } } #endregion #region MakeHashCode private const int HashSeed1 = 983; private const int HashSeed2 = 457; /// /// Génère un code de hachage pour un objet à partir des éléments spécifiés, en utilisant les valeurs de seeds spécifiées /// /// Type de l'objet pour lequel le hashcode est généré /// Objet pour lequel on génère le code de hachage (voir Remarques) /// Première seed pour générer le code de hachage /// Seconde seed pour générer le code de hachage /// Eléments à partir desquels on génère le code de hachage /// Un code de hachage pour l'objet /// Le premier paramètre obj n'est pas utilisé dans le calcul ; il n'est présent que pour /// raisons pratiques afin d'exposer la méthode comme une méthode d'extension. Il est recommandé d'utiliser /// des nombres premiers pour les valeurs des seeds, de façons à obtenir une meilleure distribution des codes /// de hachage. // ReSharper disable UnusedParameter.Global public static int MakeHashCodeWithSeeds( this T obj, int seed1, int seed2, [NotNull] IEnumerable items) // ReSharper restore UnusedParameter.Global { items.CheckArgumentNull("items"); return items .Cast() .Aggregate( seed1, (hash, item) => item != null ? unchecked(seed2 * hash ^ item.GetHashCode()) : hash); } /// /// Génère un code de hachage pour un objet à partir des éléments spécifiés. /// /// Type de l'objet pour lequel le hashcode est généré /// Objet pour lequel on génère le code de hachage (voir Remarques) /// Eléments à partir desquels on génère le code de hachage /// Un code de hachage pour l'objet /// Le premier paramètre obj n'est pas utilisé dans le calcul ; il n'est présent que pour /// raisons pratiques afin d'exposer la méthode comme une méthode d'extension. public static int MakeHashCode( this T obj, [NotNull] IEnumerable items) { return obj.MakeHashCodeWithSeeds(HashSeed1, HashSeed2, items); } /// /// Génère un code de hachage pour un objet à partir des éléments spécifiés, en utilisant les valeurs de seeds spécifiées /// /// Type de l'objet pour lequel le hashcode est généré /// Objet pour lequel on génère le code de hachage (voir Remarques) /// Première seed pour générer le code de hachage /// Seconde seed pour générer le code de hachage /// Premier élément à partir duquel générer le code de hachage /// Second élément à partir duquel générer le code de hachage /// Autres éléments à partir desquels on génère le code de hachage /// Un code de hachage pour l'objet /// Le premier paramètre obj n'est pas utilisé dans le calcul ; il n'est présent que pour /// raisons pratiques afin d'exposer la méthode comme une méthode d'extension. Il est recommandé d'utiliser /// des nombres premiers pour les valeurs des seeds, de façons à obtenir une meilleure distribution des codes /// de hachage. public static int MakeHashCodeWithSeeds( this T obj, int seed1, int seed2, object first, object second, [NotNull] params object[] others) { others.CheckArgumentNull("others"); return obj.MakeHashCodeWithSeeds(seed1, seed2, new[] { first, second }.Concat(others)); } /// /// Génère un code de hachage pour un objet à partir des éléments spécifiés. /// /// Type de l'objet pour lequel le hashcode est généré /// Objet pour lequel on génère le code de hachage (voir Remarques) /// Premier élément à partir duquel générer le code de hachage /// Second élément à partir duquel générer le code de hachage /// Autres éléments à partir desquels on génère le code de hachage /// Un code de hachage pour l'objet /// Le premier paramètre obj n'est pas utilisé dans le calcul ; il n'est présent que pour /// raisons pratiques afin d'exposer la méthode comme une méthode d'extension. public static int MakeHashCode( this T obj, object first, object second, params object[] others) { others.CheckArgumentNull("others"); return obj.MakeHashCodeWithSeeds(HashSeed1, HashSeed2, new[] { first, second }.Concat(others)); } #endregion #region Misc /// /// Obtient le service du type spécifié /// /// Type de service souhaité /// IServiceProvider qui fournit le service /// Un service du type souhaité public static T GetService([NotNull] this IServiceProvider provider) { provider.CheckArgumentNull("provider"); return (T)provider.GetService(typeof(T)); } /// /// Convertit un objet vers le type spécifié. Lève une exception si la conversion /// n'est pas possible. /// /// Type de destination /// Object à convertir /// La valeur convertie public static T ConvertTo(this object value) { return (T)Convert.ChangeType(value, typeof(T), null); } static readonly char[] _hexDigits = "0123456789abcdef".ToCharArray(); /// /// Renvoie la représentation hexadécimale d'un tableau d'octets /// /// Un tableau d'octets /// La représentation hexadécimale du tableau d'octets, avec des lettres minuscules et sans séparateur public static string ToHexString([NotNull] this byte[] bytes) { bytes.CheckArgumentNull("bytes"); char[] digits = new char[bytes.Length * 2]; for (int i = 0; i < bytes.Length; i++) { int d1, d2; d1 = CH.Math.DivRem(bytes[i], 16, out d2) ; digits[2 * i] = _hexDigits[d1]; digits[2 * i + 1] = _hexDigits[d2]; } return new string(digits); } /// /// Renvoie la représentation en base64 d'un tableau d'octets /// /// Un tableau d'octets /// La représentation base64 du tableau d'octets public static string ToBase64String([NotNull] this byte[] bytes) { bytes.CheckArgumentNull("bytes"); return Convert.ToBase64String(bytes); } /// /// Formate un objet en utilisant le modèle fournit en paramètre. /// /// Type de l'objet à formater /// Objet à formater /// Modèle à utiliser pour formater l'objet /// L'objet formaté selon le modèle fourni /// Voir la classe StringTemplate pour plus de détails sur la syntaxe du modèle. public static string FormatWithTemplate( [NotNull] this T obj, [NotNull] string template) { // ReSharper disable CompareNonConstrainedGenericWithNull if (obj == null) throw new ArgumentNullException("obj"); // ReSharper restore CompareNonConstrainedGenericWithNull template.CheckArgumentNull("template"); return StringTemplate.Format(template, obj); } /// /// Ajoute un offset à la valeur d'un pointeur /// /// Pointeur auquel ajouter l'offset /// Offset à ajouter /// Nouveau pointeur qui reflète l'addition de l'offset au pointeur public static IntPtr Add(this IntPtr ptr, int offset) { if (IntPtr.Size == 8) return new IntPtr(ptr.ToInt64() + offset); return new IntPtr(ptr.ToInt32() + offset); } /// /// Soustrait un offset de la valeur d'un pointeur /// /// Pointeur duquel soustraire l'offset /// Offset à soustraire /// Nouveau pointeur qui reflète la soustraction de l'offset du pointeur public static IntPtr Subtract(this IntPtr ptr, int offset) { if (IntPtr.Size == 8) return new IntPtr(ptr.ToInt64() - offset); return new IntPtr(ptr.ToInt32() - offset); } #endregion } }