Pour tout type donné, je veux connaître sa valeur par défaut.
En C #, il existe un mot clé appelé default pour le faire, comme
object obj = default(Decimal);
mais j'ai une instance de Type (appelée myType) et si je le dis,
object obj = default(myType);
ça ne marche pas
Y a-t-il un bon moyen de faire cela? Je sais qu'un énorme bloc commutateur fonctionnera mais ce n'est pas un bon choix.
Il n'y a en réalité que deux possibilités: null
pour les types de référence et new myType()
pour les types de valeur (ce qui correspond à 0 pour int, float, etc.). Vous n'avez donc besoin de prendre en compte que deux cas:
object GetDefaultValue(Type t)
{
if (t.IsValueType)
return Activator.CreateInstance(t);
return null;
}
(Les types de valeur ayant toujours un constructeur par défaut, cet appel à Activator.CreateInstance n'échouera jamais).
Vous pouvez également l'ajouter en tant que méthode d'extension à System.Type:
public static class TypeExtensions
{
public static object GetDefaultValue(this Type t)
{
if (t.IsValueType && Nullable.GetUnderlyingType(t) == null)
return Activator.CreateInstance(t);
else
return null;
}
}
Ayant résolu ce problème dans mes propres systèmes, voici une méthode pour déterminer correctement la valeur par défaut d’un Type arbitraire au moment de l’exécution, qui a été testée contre des milliers de Types:
/// <summary>
/// [ <c>public static object GetDefault(this Type type)</c> ]
/// <para></para>
/// Retrieves the default value for a given Type
/// </summary>
/// <param name="type">The Type for which to get the default value</param>
/// <returns>The default value for <paramref name="type"/></returns>
/// <remarks>
/// If a null Type, a reference Type, or a System.Void Type is supplied, this method always returns null. If a value type
/// is supplied which is not publicly visible or which contains generic parameters, this method will fail with an
/// exception.
/// </remarks>
/// <example>
/// To use this method in its native, non-extension form, make a call like:
/// <code>
/// object Default = DefaultValue.GetDefault(someType);
/// </code>
/// To use this method in its Type-extension form, make a call like:
/// <code>
/// object Default = someType.GetDefault();
/// </code>
/// </example>
/// <seealso cref="GetDefault<T>"/>
public static object GetDefault(this Type type)
{
// If no Type was supplied, if the Type was a reference type, or if the Type was a System.Void, return null
if (type == null || !type.IsValueType || type == typeof(void))
return null;
// If the supplied Type has generic parameters, its default value cannot be determined
if (type.ContainsGenericParameters)
throw new ArgumentException(
"{" + MethodInfo.GetCurrentMethod() + "} Error:\n\nThe supplied value type <" + type +
"> contains generic parameters, so the default value cannot be retrieved");
// If the Type is a primitive type, or if it is another publicly-visible value type (i.e. struct/enum), return a
// default instance of the value type
if (type.IsPrimitive || !type.IsNotPublic)
{
try
{
return Activator.CreateInstance(type);
}
catch (Exception e)
{
throw new ArgumentException(
"{" + MethodInfo.GetCurrentMethod() + "} Error:\n\nThe Activator.CreateInstance method could not " +
"create a default instance of the supplied value type <" + type +
"> (Inner Exception message: \"" + e.Message + "\")", e);
}
}
// Fail with exception
throw new ArgumentException("{" + MethodInfo.GetCurrentMethod() + "} Error:\n\nThe supplied value type <" + type +
"> is not a publicly-visible type, so the default value cannot be retrieved");
}
Dans ces exemples, la méthode GetDefault est implémentée dans la classe statique DefaultValue. Appelez cette méthode avec une instruction comme:
object Default = DefaultValue.GetDefault(someType);
Pour utiliser la méthode GetDefault en tant que méthode d'extension pour Type, appelez-la comme suit:
object Default = someType.GetDefault();
Cette seconde approche, Type-extension, est une syntaxe plus simple en code client, car elle évite de référencer le qualificateur de classe DefaultValue contenant l'appel.
La forme d'exécution ci-dessus de GetDefault fonctionne avec une sémantique identique à celle du mot-clé 'default' primitif C # et produit les mêmes résultats.
Pour utiliser une forme générique de GetDefault, vous pouvez accéder à la fonction suivante:
/// <summary>
/// [ <c>public static T GetDefault< T >()</c> ]
/// <para></para>
/// Retrieves the default value for a given Type
/// </summary>
/// <typeparam name="T">The Type for which to get the default value</typeparam>
/// <returns>The default value for Type T</returns>
/// <remarks>
/// If a reference Type or a System.Void Type is supplied, this method always returns null. If a value type
/// is supplied which is not publicly visible or which contains generic parameters, this method will fail with an
/// exception.
/// </remarks>
/// <seealso cref="GetDefault(Type)"/>
public static T GetDefault<T>()
{
return (T) GetDefault(typeof(T));
}
Un appel au formulaire générique pourrait être quelque chose comme:
int? inDefaultVal = DefaultValue.GetDefault<int?>();
Bien entendu, la forme générique ci-dessus de GetDefault n'est pas nécessaire pour C #, car elle fonctionne de la même manière que la valeur par défaut (T). Cela n'est utile que pour un langage .NET qui ne prend pas en charge le mot clé 'default' mais qui prend en charge les types génériques. Dans la plupart des cas, la forme générique est inutile.
Une méthode corollaire utile consiste à déterminer si un objet contient la valeur par défaut pour son type. Je m'appuie également sur la méthode IsObjectSetToDefault suivante à cette fin:
/// <summary>
/// [ <c>public static bool IsObjectSetToDefault(this Type ObjectType, object ObjectValue)</c> ]
/// <para></para>
/// Reports whether a value of type T (or a null reference of type T) contains the default value for that Type
/// </summary>
/// <remarks>
/// Reports whether the object is empty or unitialized for a reference type or nullable value type (i.e. is null) or
/// whether the object contains a default value for a non-nullable value type (i.e. int = 0, bool = false, etc.)
/// <para></para>
/// NOTE: For non-nullable value types, this method introduces a boxing/unboxing performance penalty.
/// </remarks>
/// <param name="ObjectType">Type of the object to test</param>
/// <param name="ObjectValue">The object value to test, or null for a reference Type or nullable value Type</param>
/// <returns>
/// true = The object contains the default value for its Type.
/// <para></para>
/// false = The object has been changed from its default value.
/// </returns>
public static bool IsObjectSetToDefault(this Type ObjectType, object ObjectValue)
{
// If no ObjectType was supplied, attempt to determine from ObjectValue
if (ObjectType == null)
{
// If no ObjectValue was supplied, abort
if (ObjectValue == null)
{
MethodBase currmethod = MethodInfo.GetCurrentMethod();
string ExceptionMsgPrefix = currmethod.DeclaringType + " {" + currmethod + "} Error:\n\n";
throw new ArgumentNullException(ExceptionMsgPrefix + "Cannot determine the ObjectType from a null Value");
}
// Determine ObjectType from ObjectValue
ObjectType = ObjectValue.GetType();
}
// Get the default value of type ObjectType
object Default = ObjectType.GetDefault();
// If a non-null ObjectValue was supplied, compare Value with its default value and return the result
if (ObjectValue != null)
return ObjectValue.Equals(Default);
// Since a null ObjectValue was supplied, report whether its default value is null
return Default == null;
}
La méthode IsObjectSetToDefault
ci-dessus peut être appelée sous sa forme native ou accédée en tant qu'extension Type-class.
Que diriez-vous de quelque chose comme ...
class Program
{
static void Main(string[] args)
{
PrintDefault(typeof(object));
PrintDefault(typeof(string));
PrintDefault(typeof(int));
PrintDefault(typeof(int?));
}
private static void PrintDefault(Type type)
{
Console.WriteLine("default({0}) = {1}", type,
DefaultGenerator.GetDefaultValue(type));
}
}
public class DefaultGenerator
{
public static object GetDefaultValue(Type parameter)
{
var defaultGeneratorType =
typeof(DefaultGenerator<>).MakeGenericType(parameter);
return defaultGeneratorType.InvokeMember(
"GetDefault",
BindingFlags.Static |
BindingFlags.Public |
BindingFlags.InvokeMethod,
null, null, new object[0]);
}
}
public class DefaultGenerator<T>
{
public static T GetDefault()
{
return default(T);
}
}
Il produit la sortie suivante:
default(System.Object) =
default(System.String) =
default(System.Int32) = 0
default(System.Nullable`1[System.Int32]) =
Qu'entendez-vous par "valeur par défaut"? Tous les types de référence ("classe") ont la valeur par défaut NULL, tandis que tous les types de valeur auront leurs valeurs par défaut conformément à cette table .
Voici une fonction qui renverra la valeur par défaut pour un type nullable (autrement dit, elle renvoie 0 pour les deux Decimal
et Decimal?
):
public static object DefaultValue(Type maybeNullable)
{
Type underlying = Nullable.GetUnderlyingType(maybeNullable);
if (underlying != null)
return Activator.CreateInstance(underlying);
return Activator.CreateInstance(maybeNullable);
}