David Rodrigues
David Rodrigues

Reputation: 12532

Setting the default(class) value

It's possible I set a default() value for MyClass? My idea is that, instead if return null when I call default(MyClass), it returns a instance of this class, but with default values.

For instance:

class MyClass {
     // Default value.
     static public readonly MyClass Default = new MyClass();
}

So when I call:     var defaultValue = default(MyClass);
It to be similar to: var defaultValue = MyClass.Default;

I need to use it with generics:

class DictionaryEx<TKey, TValue>: Dictionary<TKey, TValue> {
    public TValue GetOrNull(TKey paramKey) {
        return ContainsKey(paramKey) ? this[paramKey] : default(TValue);
    }
} 

So if I use GetOrNull() with an inexistent key, it'll not return null, but the default instance of TValue (MyClass.Default).

Upvotes: 4

Views: 3775

Answers (2)

Jcl
Jcl

Reputation: 28272

That's not the aim of the default keyword, and I'm glad you can't redefine it: you'd make things really complicated.

The default keyword is there so that when using value types (which can't be null) you get a default object state (for reference types, null, and for value types, the parameterless constructor state). That is a well-defined behaviour and if you changed it you could cause much grief.

Value-types have an implicit public parameterless constructor if you didn't define one (so default(T) can actually return a value), where all its fields are initialized to their default() values (check it if you want: define a struct with a constructor that takes parameters and no parameterless constructor, and try new myStruct(): it compiles and works). That's not the case for reference types... if a class doesn't have an explicit public parameterless constructor (or no constructor defined at all), there's no way you can instantiate it without arguments.

Think about it for a moment, in case you changed the default() implementation, what could this code do (take the default implementation syntax as pseudocode)?

public abstract class Foo
{
   protected Foo(int x)
   {
   }      
   protected override Foo default()
   {
     return new Foo(50); // You can't instantiate an abstract class
   }
}
//..
Foo myFoo = default(Foo);

How is default(Foo) suppossed to instantiate a Foo at all, if it's abstract? But to make it even more complicated... imagine you have this value type, which -needs- to be able to instantiate with default():

public struct Bar
{
   public Foo myFoo;
}
//
var myBar = default(Bar); // this just can't return null since
                          // Bar is a value type

What could possibly (other than null) be the value of myOtherFoo.myFoo if Foo is abstract?

But let's make it easier and take abstract out of the question (let's pretend it wouldn't let you change default() in abstract classes and would give a compiler error), and think about this case:

public class Foo
{
   public Bar myBar;      
   protected override Foo default()
   {
     return new Foo();
   }
}
public struct Bar
{
   public Foo myFoo;      
}
var myBar = default(Bar);

You have just created and endless initialization loop.

This could be of course both statically and runtime analyzed and both compile-time errors (like those where you define a struct field inside the same struct type definition) and runtime exceptions could be thrown... but you are just taking away the well-documented behaviour of default().

Possible workaround

If you want to emulate the behaviour of default(T) in structs on classes, then just replace default(T) for new T() and add a T: new() constraint. This would require those classes to have a parameterless constructor and not be abstract, but you can think of it as your "default(T)" implementation if you wish (without those two constraints, there'd be no way any default() method involving creating an instance would work either).

In your example, this would be:

class DictionaryEx<TKey, TValue>: Dictionary<TKey, TValue>
  where TValue : new()
  {
    public TValue GetOrDefault(TKey paramKey) {
      return ContainsKey(paramKey) ? this[paramKey] : new TValue();
  }
} 

This would, however, disallow returning null. As a possible workaround, you could "tag" the classes where you'd like NOT to return null with an empty interface and instead of using the new() constraint, use Activator.CreateInstance() when the type implements that interface, or return null otherwise... something like:

interface IWithExplicitParameterLessConstructor {}

class DictionaryEx<TKey, TValue>: Dictionary<TKey, TValue>
{
    public TValue GetOrDefault(TKey paramKey) {
        return ContainsKey(paramKey) ? this[paramKey] : 
            (typeof(IWithExplicitParameterLessConstructor).IsAssignableFrom(typeof(TValue)) ? Activator.CreateInstance<TValue>() : default(TValue));
    }
}

Then for classes "tagged" with that interface, it'll create a default instance (using a parameterless constructor: it'll throw an exception is none is defined), and would return null for all others (and the corresponding default value for value types).

Otherwise, if you NEED to be able to have a default instance value without a public parameterless constructor (this smells from afar), you could document a convention in your code, and use reflection to find a static property value or static method in the class type. As stated, this smells from afar, and I definitely not recommend it, but it is doable:

public static class GetDefaultHelper
{
    public static T GetDefaultInstance<T>()
    {
      var propInfo = typeof(T).GetProperty("Default", BindingFlags.Public | BindingFlags.Static);
      return (propInfo != null) ?  (T)propInfo.GetValue(null) : default(T);     
    }
}

And use it like:

Foo foo = GetDefaultHelper.GetDefaultInstance<Foo>();

Fiddle here: https://dotnetfiddle.net/JO8YfV

Upvotes: 3

denys-vega
denys-vega

Reputation: 3697

Add a new parameter of type TValue for default value.

public class DictionaryEx<TKey, TValue> : Dictionary<TKey, TValue>
{
    public TValue GetValueOrDefault(TKey paramKey, TValue @default)
    {
        TValue value;
        if (TryGetValue(paramKey, out value))
            return value;

        return @default;
    }
}

Solution 2:

public class DictionaryEx<TKey, TValue> : Dictionary<TKey, TValue>
{
    public TValue GetValueOrDefault(TKey paramKey, Func<TValue> funcDefault)
    {
        TValue value;
        if (TryGetValue(paramKey, out value))
            return value;

        return funcDefault();
    }

    public TValue GetValueOrDefault(TKey paramKey)
    {
        return GetValueOrDefault(paramKey, Activator.CreateInstance<TValue>);
    }
}

Upvotes: 0

Related Questions