views:

155

answers:

5

I've written a simple abstract generic class in C# (.NET 2.0) and I preferably want to limit it to only reference types, so I can indicate no value by a null. However, I also want to use types such as long and decimal, why don't allow null (being structs after all). I considered making the class

public abstract Field<Nullable<T>>
{

}

But that would prevent my use of the string type, which is a class. How can I box up my decimals and longs so I can use them in this generic.

 abstract class Field<T>
 {
    private int _Length = 1;
    private bool _Required = false;
    protected T _Value; //= null;

    public int Length
    {
        get { return _Length; }
        private set
        {
            if (value < 1) throw new ArgumentException("Field length must be at least one.");
            _Length = value;
        }
    }

    public bool Required
    {
        get { return _Required; }
        private set { _Required = value; }
    }

    public abstract string GetFieldValue();
    public abstract void ParseFieldValue(string s);

    public virtual T Value
    {
        get { return _Value; }
        set
        {
            if (value == null && Required)
                throw new ArgumentException("Required values cannot be null.");
            _Value = value;
        }
    }

}

Please note that I need to represent numeric 0 and null differently. For this reason default(T) will not work.

A: 

Try this:

public abstract Field<T>
    where T : class
{

}

This will restrict the generic type parameter to be a reference type. This is the only way that you will be able to return null from this method. Yes, this will prevent you from passing value types to the method.

Andrew Hare
This will not allow longs or decimals, as specifically stated as required in the question. Please read before posting.
C. Ross
@C. Ross - I did read before posting. If you re-read my answer you will notice that I point out that the only way to return _null_ from a generic method that returns a value that is the type of one of the generic arguments is to constrain that argument with "class".
Andrew Hare
+3  A: 

The whole point of generics (among others) is to avoid boxing. See this:

private bool _Required = false;
protected T _Value = default(T);

If you need to distinguish between "0" and "not set", object is your only way out:

protected object _Value;

And then box-unbox-box-unbox.

Anton Gogolev
A good answer, but unfortunately I need to represent 0 and "No value" separately.
C. Ross
A: 

It's a good question. I wish this were possible:

class MyClass<T> where T : struct {
    T? value;
    ...
}
class MyClass<T> where T : class {
    T value;
    ...
}

and the compiler would choose the correct generic class depending on whether the constraints are satisfied.

Unfortunately, it doesn't work, which can cause problems with automatic source code generation.

erikkallen
+2  A: 

You would need two classes

abstract class FieldR<T> where T: class
{
  T Value { get {} set {} }
}

abstract class FieldV<T> where T: struct
{
  Nullable<T> Value { get {} set {} }
}

The first class would use

T

While the second class would use

Nullable<T>
Stevo3000
This may actually work if the both descend from a common non-generic ancestor (Field) ...
C. Ross
@ C. Ross - Yes, that may well work. I've used a non generic base class to enable branching of inheritance before.
Stevo3000
+1  A: 

I'm not sure if you can constrain the generic parameters appropriately at compile time, but you could add in some runtime checks to only allow reference types along with the desired subset of nullable value types:

public virtual T Value
{
    get { return _Value; }
    set
    {
        Type t = typeof(T);
        if (t.IsValueType)
        {
            if (t.IsGenericType
                && (t.GetGenericTypeDefinition() == typeof(Nullable<>)))
            {
                Type u = Nullable.GetUnderlyingType(t);
                if ((u != typeof(long)) && (u != typeof(decimal)))
                {
                    throw new ArgumentException(
                        "Only long? and decimal? permitted!");
                }
            }
            else
            {
                throw new ArgumentException("Only nullable types permitted!");
            }
        }

        if ((value == null) && Required)
        {
            throw new ArgumentException("Required values cannot be null!");
        }

        _Value = value;
    }
}

(In reality, you'd probably want to put your type checks in a constructor rather than the Value setter.)

LukeH