WCF supports using enum types that are tagged with the FlagsAttribute as parameters within an UriTemplate. Like this:
[DataContract]
[Flags]
public enum OptionsEnum
{
[EnumMember]
None = 0,
[EnumMember]
MyOption1 = 1,
[EnumMember]
MyOption2 = 2,
[EnumMember]
MyOption3 = 4,
[EnumMember]
MyOption4 = 8
}
[ServiceContract]
public interface MyServiceContract
{
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "resource?options={options}")]
void MyOperation(OptionsEnum options);
}
The resource can then be requested via URLs like this:
GET /resource?options=None
GET /resource?options=MyOption1
GET /resource?options=MyOption1,MyOption3
All of this works really nicely as long the URL actually contains a value for the options parameter. However, if I request the resource without specifying a value in the URL, like this:
GET /resource
I get an exception with the message Value cannot be null.\r\nParameter name: value
and the following stack trace:
at System.Enum.TryParseEnum(Type enumType, String value, Boolean ignoreCase, EnumResult& parseResult)
at System.Enum.Parse(Type enumType, String value, Boolean ignoreCase)
at System.ServiceModel.Dispatcher.QueryStringConverter.ConvertStringToValue(String parameter, Type parameterType)
at System.ServiceModel.Dispatcher.UriTemplateDispatchFormatter.DeserializeRequest(Message message, Object[] parameters)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.DeserializeInputs(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
Obviously, this is because QueryStringConverter passes null into Enum.Parse(...) in this case. As a result the implementation of MyServiceContract will not be executed.
Of course I could just switch to string for the type of the options parameter and do all the parsing stuff myself within the service implementation, but that's not what I want, really.
Does anyone know a clean solution to have OptionsEnum.None passed into the service implementation if the URL does not contain a value (just like 0 is passed for omitted parameters of type int)?
I already tried using a custom TypeConverter implementation, but even that does not seem to work. Looking at the implementation of QueryStringConverter it seems like it will always try to convert enum types by itself.
Ok, I have found a solution that is reusable and does not involve switching to string for the type of the flags parameter. I was hoping for something simpler, though. Anyway, in case it helps someone else, here it is.
The approach is relatively simple:
Wrap the enum in a reference type.
Use a TypeConverter to customize the process of converting the value from string to our flags enum as implemented by WCF's QueryStringConverter.
/// <summary>
/// Wraps a flags enum value.
/// </summary>
/// <remarks>
/// This class is meant to be used in conjunction with
/// <see cref="FlagsConverter{TFlags,TEnum}"/> and simply boxes an enum.
/// This is necessary in order to customize WCF's default behavior for enum
/// types (as implemented by <see cref="QueryStringConverter"/>) by using a
/// <see cref="TypeConverter"/>.
/// </remarks>
/// <devdoc>
/// We prefer this over using an 1-Tuple (<see cref="Tuple{T1} "/>) as it
/// allows us to add constraints on the type parameter. Also, the value
/// wrapped by a <see cref="Tuple{T1} "/> is read-only, which we don't want
/// here, as there is no reason to prevent [OperationContract] methods from
/// writing the wrapped <see cref="Value"/>.
/// </devdoc>
/// <typeparam name="TEnum">
/// The enum type. Must be attributed with <see cref="FlagsAttribute"/>.
/// </typeparam>
public abstract class Flags<TEnum>
where TEnum : struct, IConvertible
{
// Use a static c'tor to add run-time checks on the type parameter that
// cannot be checked at compile-time via constraints.
static Flags()
{
if (!typeof(TEnum).IsEnum)
{
throw new InvalidOperationException("T is not an enum");
}
if (!typeof(TEnum).IsDefined(typeof(FlagsAttribute), false))
{
throw new InvalidOperationException("T is not a flags enum");
}
}
/// <summary>
/// The enum value.
/// </summary>
public TEnum Value
{
get;
set;
}
}
/// <summary>
/// A <see cref="TypeConverter"/> implementation that converts from
/// <c>string</c> to <see cref="Flags{TEnum}"/>.
/// </summary>
/// <remarks>
/// <para>
/// Meant to be used in conjunction with <see cref="Flags{TEnum}"/>.
/// The purpose of this <see cref="TypeConverter"/> implementation is to
/// convert both <c>null</c> and the empty string to <c>default(TEnum)</c>
/// instead of throwing an exception. This way, a flags enum (wrapped in an
/// instance of <see cref="Flags{TEnum}"/>) can be used as the type of an
/// optional parameter in a RESTful WCF <c>OperationContract</c> method.
/// </para>
/// <para>
/// If the string value (as provided by <see cref="QueryStringConverter"/>)
/// is <c>null</c> or empty or can be successfully parsed into an enum
/// value of type <typeparamref name="TEnum"/>, this implementation will
/// provide an instance of <typeparamref name="TFlags"/>. Thus, there is no
/// need to check a <typeparamref name="TFlags"/>-typed value for
/// <c>null</c> within the implementation of an <c>OperationContract</c>
/// method.
/// </para>
/// </remarks>
/// <typeparam name="TFlags">
/// A sub-class of <see cref="Flags{TEnum}"/>. Must have a default c'tor.
/// </typeparam>
/// <typeparam name="TEnum">
/// The enum type. Must be attributed with <see cref="FlagsAttribute"/>.
/// </typeparam>
public class FlagsConverter<TFlags, TEnum> : TypeConverter
where TFlags : Flags<TEnum>, new()
where TEnum : struct, IConvertible
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (string.IsNullOrEmpty((string)value))
{
// The following line is what Flags<> and FlagsConverter<,> is
// all about: Provide the enum's default value (meaning no flag
// is set) for null and the empty string instead of passing it
// into Enum.Parse() (which results in an ArgumentException).
return new TFlags { Value = default(TEnum) };
}
// Otherwise, just pass the value on the Enum.Parse(), i.e. don't
// add any further changes to WCF's default behavior as implemented
// by QueryStringConverter.
return new TFlags { Value = (TEnum)Enum.Parse(typeof(TEnum), (string)value, true) };
}
}
These two classes can now be used to solve the problem like this
(reusing the example from the original question):
[DataContract]
[Flags]
public enum OptionsEnum
{
[EnumMember]
None = 0,
[EnumMember]
MyOption1 = 1,
[EnumMember]
MyOption2 = 2,
[EnumMember]
MyOption3 = 4,
[EnumMember]
MyOption4 = 8
}
[DataContract]
[TypeConverter(typeof(FlagsConverter<MyOptionalFlags, OptionsEnum>))]
public class MyOptionalFlags
: Flags<OptionsEnum>
{
// We don't add anything here, as the whole purpose of this class is to
// wrap the OptionsEnum in a class that will be instantiated by the
// attributed FlagsConverter.
}
[ServiceContract]
public interface MyServiceContract
{
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "resource?options={options}")]
void MyOperation(MyOptionalFlags options);
}
public class MyServiceContractImpl
: MyServiceContract
{
public void MyOperation(MyOptionalFlags options)
{
if (options.Value == OptionsEnum.None)
{
// We will now get here for requests that do not specify a
// value for the options parameter in the URL. Note that just
// like for an enum value, we don't have to check if options is
// null here.
}
}
}
Related
I have Enum:
public enum SomeType
{
TypeA,
TypeB,
TypeC
}
but in MongoDB i would like this map to:
type_a type_b type_c
I'm using EnumRepresentationConvention(BsonType.String)
I tried:
public enum SomeType
{
[BsonElement("type_a")]
TypeA,
[BsonElement("type_b")]
TypeB,
[BsonElement("type_c")]
TypeC
}
but this doesn't work. Im getting exception:
Requested value 'type_a' was not found.
Is anyone know how can achieve such mapping in MongoDb C# driver?
UPDATE
So I wrote a new serializer that does what you need. I built it off some code I wrote as part of SharpExtensions. It certainly isn't optimized (or as simplified as it could be) but it works.
First I created a sample class Foo and reused your example Enum. I then leveraged the DescriptionAttribute to specify an alternate representation of the Enum that you completely control. Though this could be potentially simplified if you leverage something like Humanizer to consistently change the representation.
I then created a BsonSerializationProvider to let the driver know when it should use that serializer (much like my original answer). The meat is in EnumDescriptionSerializer which uses reflection to find the string representation of a particular value of SomeType. This is where I leverage the boilerplate code from SharpExtensions to move between the string and the actual Enum value. You'll notice the code will also work with EnumMemberAttribute as well as DescriptionAttribute. Please feel free to import the SharpExtensions library if you don't want to use the boilerplate code directly.
public class Foo
{
public ObjectId Id {get;set;}
public SomeType Enum {get;set;}
}
public enum SomeType
{
[Description("type_a")]
TypeA,
[Description("type_b")]
TypeB,
[Description("type_c")]
TypeC
}
public class EnumDescriptionSerializerProvider : BsonSerializationProviderBase
{
public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry registry)
{
if (!type.GetTypeInfo().IsEnum) return null;
var enumSerializerType = typeof(EnumDescriptionSerializer<>).MakeGenericType(type);
var enumSerializerConstructor = enumSerializerType.GetConstructor(new Type[0]);
var enumSerializer = (IBsonSerializer)enumSerializerConstructor?.Invoke(new object[0]);
return enumSerializer;
}
}
public class EnumDescriptionSerializer<TEnum> : StructSerializerBase<TEnum> where TEnum : struct
{
public BsonType Representation => BsonType.String;
public override TEnum Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var valAsString = context.Reader.ReadString();
var enumValue = valAsString.GetValueFromDescription<TEnum>();
return enumValue;
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TEnum value)
{
context.Writer.WriteString(value.GetDescription());
}
}
public static class EnumExtensions
{
public enum StringCase
{
/// <summary>
/// The default capitalization
/// </summary>
Default,
/// <summary>
/// Lower Case, ex. i like widgets.
/// </summary>
[Description("Lower Case")]
Lower,
/// <summary>
/// Upper Case, ex. I LIKE WIDGETS.
/// </summary>
[Description("Upper Case")]
Upper,
/// <summary>
/// Lower Camelcase, ex: iLikeWidgets.
/// </summary>
[Description("Lower Camelcase")]
LowerCamel,
/// <summary>
/// Upper Camelcase, ex: ILikeWidgets.
/// </summary>
[Description("Upper Camelcase")]
UpperCamel
}
/// <summary>
/// Get the value of an enum as a string.
/// </summary>
/// <param name="val"> The enum to convert to a <see cref="string"/>. </param>
/// <param name="case"> A <see cref="StringCase"/> indicating which case to return. Valid enumerations are StringCase.Lower and StringCase.Upper. </param>
/// <exception cref="ArgumentNullException"> If the enum is null. </exception>
/// <returns></returns>
public static string GetName<TEnum>(this TEnum val, StringCase #case = StringCase.Default) where TEnum : struct
{
var name = Enum.GetName(val.GetType(), val);
if (name == null) return null;
switch (#case)
{
case StringCase.Lower:
return name.ToLower();
case StringCase.Upper:
return name.ToUpper();
default:
return name;
}
}
/// <summary>
/// Gets the description for the supplied Enum Value.
/// </summary>
/// <param name="val">The value for which to get the description attribute.</param>
/// <returns>The <see cref="string"/> description.</returns>
public static string GetDescription<TEnum>(this TEnum val) where TEnum : struct
{
var fields = val.GetType().GetTypeInfo().GetDeclaredField(GetName(val));
// first try and pull out the EnumMemberAttribute, common when using a JsonSerializer
if (fields.GetCustomAttributes(typeof(EnumMemberAttribute), false).FirstOrDefault() is EnumMemberAttribute jsonAttribute) return jsonAttribute.Value;
// If that doesn't work, do the regular description, that still fails, just return a pretty ToString().
return !(fields.GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault() is DescriptionAttribute attribute) ? GetName(val) : attribute.Description;
}
/// <summary>
/// Get the value of an <see cref="Enum"/> based on its description attribute.
/// </summary>
/// <typeparam name="T">The type of the <see cref="Enum"/>.</typeparam>
/// <param name="description">The Description attribute of the <see cref="Enum"/>.</param>
/// <returns>The value of T or default(T) if the description is not found.</returns>
public static T GetValueFromDescription<T>(this string description) where T : struct
{
if (string.IsNullOrWhiteSpace(description)) throw new ArgumentNullException(nameof(description));
var type = typeof(T);
if (!type.GetTypeInfo().IsEnum) throw new ArgumentOutOfRangeException(nameof(T), $"{typeof(T)} is not an Enum.");
var fields = type.GetRuntimeFields();
foreach (var field in fields)
{
if (field.Name == description) return (T)field.GetValue(null);
// first try and pull out the EnumMemberAttribute, common when using a JsonSerializer
if (field.GetCustomAttribute(typeof(EnumMemberAttribute), false) is EnumMemberAttribute jsonAttribute && jsonAttribute.Value == description) return (T)field.GetValue(null);
// If that doesn't work, do the regular description, that still fails, just return a pretty ToString().
if (field.GetCustomAttribute(typeof(DescriptionAttribute), false) is DescriptionAttribute attribute && attribute.Description == description) return (T)field.GetValue(null);
}
throw new Exception($"Failed to parse value {description} into enum {typeof(T)}");
}
}
I wrote a simple test inserti
ng several Foo documents into a collection. This is how they look in the database
> db.enum.find()
{ "_id" : ObjectId("5c76c0240bba918778cc6b7f"), "Enum" : "type_a" }
{ "_id" : ObjectId("5c76c0580bba918778cc6b80"), "Enum" : "type_a" }
{ "_id" : ObjectId("5c76c05d0bba918778cc6b81"), "Enum" : "type_b" }
I also verified that they round-trip correctly. I haven't run any tests beyond some simple code using LINQPad. I believe this is what you are looking for.
ORIGINAL ANSWER
I wrote a custom serializer for this so I could register it and things "just work".
public class EnumAsStringSerializationProvider : BsonSerializationProviderBase
{
public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry registry)
{
if (!type.GetTypeInfo().IsEnum) return null;
var enumSerializerType = typeof(EnumSerializer<>).MakeGenericType(type);
var enumSerializerConstructor = enumSerializerType.GetConstructor(new[] { typeof(BsonType) });
var enumSerializer = (IBsonSerializer) enumSerializerConstructor?.Invoke(new object[] { BsonType.String });
return enumSerializer;
}
}
Then I register it with the BsonSerializer.
var enumAsStringSerializationProvider = new EnumAsStringSerializationProvider();
BsonSerializer.RegisterSerializationProvider(enumAsStringSerializationProvider);
For me, it just works and I don't need to remember to decorate enums.
Currently, I am working on IEGL10 in Xamarin. I have implemented ISurfaceHolderCallback and on SurfaceCreated(ISurfaceHolder holder) I have to call a method like this.
public void SurfaceCreated(ISurfaceHolder holder)
{
mEglSurface = mEgl.EglCreateWindowSurface(mEglDisplay, mEglConfig,
holder, null);
}
The problem is, the holder is a C# interface and EglCreateWindowSurface requires Java.Lang.Object. So how can I do the casting. If I directly cast holder like (Java.Lang.Object)holder. It is throwing invalid cast exception.
Please help guys I am really stuck here.
How to cast C# interface to Java.Lang.Object?
MonoDroid has integrated extension for this purpose :
Java.Lang.Object holder_object = holder.JavaCast<Java.Lang.Object>();
EGLSurface mEglSurface = mEgl.EglCreateWindowSurface(mEglDisplay, mEglConfig, holder_object, null);
You could see the document :
public static class Extensions
{
//
// Summary:
// /// Performs an Android runtime-checked type conversion. ///
//
// Parameters:
// instance:
// /// An Android.Runtime.IJavaObject instance to convert /// to a TResult instance.
// ///
//
// Type parameters:
// TResult:
// /// The type to convert instance to. /// TResult must implement the /// Android.Runtime.IJavaObject
// interface. ///
//
// Returns:
// /// A TResult representation for /// instance. ///
//
// Exceptions:
// T:System.ArgumentException:
// ///
// /// The JNI class for TResult cannot be found. ///
// ///
// -or-
// ///
// /// The proxy class for TResult is /// abstract, and the non-abstract Proxy can't
// be found. ///
// ///
//
// T:System.InvalidCastException:
// /// The Anrdroid object instance instance.Handle /// cannot be converted to the
// Android type corresponding to /// TResult. ///
//
// T:System.NotSupportedException:
// /// An unknown error occurred. ///
//
// Remarks:
// /// /// This is a hack, but a currently necessary one. /// ///
// /// Most of the Android types are staticly generated /// wrappers over a description
// of the underlying Android types. This /// intermediate description does not expose
// implementation details, /// which sometimes must be relied upon. ///
// ///
// /// For example, consider the /// Javax.Microedition.Khronos.Egl.EGLContext.EGL
// /// property, which returns an instance of the /// Javax.Microedition.Khronos.Egl.IEGL
// /// interface. This interface is useless, containing no members to /// invoke
// or use. The developer is instead expected to convert this /// instance to an
// interface which contains actual operations, such as /// the Javax.Microedition.Khronos.Egl.IEGL10
// interface. /// Unfortunately, the MonoDroid-generated wrappers do not know this,
// /// nor can they (the EGL10 implementation may be removed in a /// future Android
// version). The result is that if developers attempt /// to cast within managed
// code, the result will be a /// System.InvalidCastException: ///
// /// EGL10 egl10 = (EGL10) EGLContext.EGL; // throws ///
// /// The JavaCast() method allows performing such type conversions /// while bypassing
// the managed type system and instead relying upon /// the Android runtime system
// to perform the type checking. This /// allows: ///
// /// EGL10 egl10 = EGLContext.EGL.JavaCast<EGL10>(); // good ///
public static TResult JavaCast<TResult>(this IJavaObject instance) where TResult : class, IJavaObject;
}
I have a Dictionary containing strings as keys, and objects as values in an abstract class.
I have two classes deriving from this abstract class.
One of the deriving classes works perfectly, all configurations and items are loaded and retrievable without issues.
However, the other class is giving me headaches.
When I try to get an object of type "Domain"; I get an invalid cast exception, although I am adding the value to the dictionary as said type.
Here is the code:
public sealed class UserDomainConfig: ConfigParser {
public UserDomainConfig(string configFilePath) : base(configFilePath) { }
public Domain GetConfig(string key) => GetConfig<Domain>(key);
public override bool LoadConfigs() {
return base.LoadConfigs();
}
public UserDomainConfig SetConfig(string key, Domain value) {
base.SetConfig(key, value);
return this;
}
}
public abstract class ConfigParser: IConfig {
/* Snip */
/// <summary>
/// Gets the config.
/// </summary>
/// <returns>The config.</returns>
/// <param name="key">Key.</param>
/// <typeparam name="T">The 1st type parameter.</typeparam>
public virtual T GetConfig<T>(string key) {
object output = null;
try {
if (!configs.TryGetValue(key, out output))
return default(T);
//return (T)output;
//return output as T;
// This is where the exception is occurring.
// I've tried multiple solutions to try to remedy this issue.
return (T)Convert.ChangeType(output, typeof(T));
} catch (InvalidCastException ex) {
logger.Error($"Failed to cast config { key }!");
}
return default(T);
}
/// <summary>
/// Sets the config.
/// </summary>
/// <returns>The config.</returns>
/// <param name="key">Key.</param>
/// <param name="value">Value.</param>
/// <typeparam name="T">The 1st type parameter.</typeparam>
public virtual IConfig SetConfig<T>(string key, T value) {
if (KeyExists(key))
configs.Remove(key);
configs.Add(key, value);
return this;
}
Any ideas on how to fix this, and/or why this isn't working in the first place, although it works like a charm with strings, bools, and ints?
The Convert class only supports simple types, known by .NET, like Int32, String, DateTime. It does not support user defined or complex types like Domain. If you try to convert something to a not-supported type, the method Convert.ChangeType throws an InvalidCastException. The only exception is that it will work if the Original value (output) is already of that desired type; than no actual conversion is needed.
For more information, read: https://msdn.microsoft.com/en-us/library/dtb69x08(v=vs.110).aspx
If you are certain the stored value is of the type Domain, than log more information. Something like this:
logger.Error($"Failed to cast config { key } of type { output.GetType() } to type { typeof(T) }!");
This way you can verify your claim that both types are the same.
I have the following code w/comments: (It does compile)
/// <summary>
/// return a passing result of a particular type
/// </summary>
/// <typeparam name="T">Type of the value to be returned</typeparam>
/// <param name="value">the value to be returned</param>
/// <returns>a passing result</returns>
public static Result<T> Pass(T value)
{
return new Result<T>()
{
Passed = true,
Value = value
};
}
I get the following warning with it:
Warning 1 SA1620 : CSharp.Documentation :
The typeparam tags in the documentation header must
match the generic types for the method.
I did look at the help page for this error, which gives this explanation:
To fix a violation of this rule, add and fill-in one tag
for each generic type parameter on the element, and make sure that
the tags appear in the same order as the element’s type
parameters.
And it has provided sample code:
/// <summary>
/// A sample generic class.
/// </summary>
/// <typeparam name="S">The first generic type parameter.</typeparam>
/// <typeparam name="T">The second generic type parameter.</typeparam>
public class Class1<S, T>
{
}
I don't see anything about mine that breaks the standards it is showing, and I have tried various odd things, but I have no real idea of what I'm supposed to do here.
The only way that this can compile is if this method is inside a class that is generic in T. This method doesn't have any type parameters. If it was generic, there would be type parameters after the name of the method:
public static Result<T> Pass<T>(T value)
{
return new Result<T>()
{
Passed = true,
Value = value
};
}
But that's not the case with your method. So it must be:
class SomeClass<T>
{
public static Result<T> Pass(T value)
{
return new Result<T>()
{
Passed = true,
Value = value
};
}
}
And any documentation about the type parameter belongs up at the class level. E.g.:
/// <summary>
/// This is a result class
/// </summary>
/// <typeparam name="T">Type of the value to be returned</typeparam>
public class Result<T>
{
public bool Passed { get; set; }
public T Value { get; set; }
/// <summary>
/// return a passing result of a particular type
/// </summary>
/// <param name="value">the value to be returned</param>
/// <returns>a passing result</returns>
public static Result<T> Pass(T value)
{
return new Result<T>()
{
Passed = true,
Value = value
};
}
}
This question already has answers here:
how to get the default value of a type if the type is only known as System.Type? [duplicate]
(3 answers)
Closed 9 years ago.
For any given type i want to know its default value.
In C#, there is a keyword called default for doing this like
object obj = default(Decimal);
but I have an instance of Type (called myType) and if I say this,
object obj = default(myType);
it doesn't work
Is there any good way of doing this?
I know that a huge switch block will work but thats not a good choice.
There's really only two possibilities: null for reference types and new myType() for value types (which corresponds to 0 for int, float, etc) So you really only need to account for two cases:
object GetDefaultValue(Type t)
{
if (t.IsValueType)
return Activator.CreateInstance(t);
return null;
}
(Because value types always have a default constructor, that call to Activator.CreateInstance will never fail).
You could also add it as an extension method to 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;
}
}
Having solved this problem in my own systems, here is a method for correctly determining the default value of an arbitrary Type at run time, which has been tested against thousands of 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");
}
In these examples, the GetDefault method is implemented in the static class DefaultValue. Call this method with a statement like:
object Default = DefaultValue.GetDefault(someType);
To use the GetDefault method as an extension method for Type, call it like this:
object Default = someType.GetDefault();
This second, Type-extension approach is a simpler client-code syntax, since it removes the need to reference the containing DefaultValue class qualifier on the call.
The above run time form of GetDefault works with identical semantics as the primitive C# 'default' keyword, and produces the same results.
To use a generic form of GetDefault, you may access the following function:
/// <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));
}
A call to the generic form could be something like:
int? inDefaultVal = DefaultValue.GetDefault<int?>();
Of course, the above generic form of GetDefault is unnecessary for C#, since it works the same as default(T). It is only useful for a .NET language that does not support the 'default' keyword but which supports generic types. In most cases, the generic form is unnecessary.
A useful corollary method is one to determine whether an object contains the default value for its Type. I also rely on the following IsObjectSetToDefault method for that purpose:
/// <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;
}
The above IsObjectSetToDefault method can either be called in its native form or accessed as a Type-class extension.
How about something like...
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);
}
}
It produces the following output:
default(System.Object) =
default(System.String) =
default(System.Int32) = 0
default(System.Nullable`1[System.Int32]) =
What do you mean by "Default Value"? All reference Types ("class") have null as default value, while all value types will have their default values according to this table.
Here's a function that will return the default value for a nullable type (in other words, it returns 0 for both Decimal and Decimal?):
public static object DefaultValue(Type maybeNullable)
{
Type underlying = Nullable.GetUnderlyingType(maybeNullable);
if (underlying != null)
return Activator.CreateInstance(underlying);
return Activator.CreateInstance(maybeNullable);
}