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