views:

51

answers:

1

I'm trying to create a class to use as a field in other objects that can hold a temporary unsaved value until a SaveChanges method is called on the object, so that I can pass the value to a stored proc, which will update any non-null fields with the new value, and leave fields with null as their original values.

I want to be able to do this:

private Field<int> MyInt;
private Field<int?> MyNullableInt;
private Field<string> MyString;

and then be able to use the Field objects like this:

MyInt = new Field<int>(1);    // initialise with a value
MyInt.Value = 2;              // assign a new value internally marking it as having a new value
int x = MyInt.Value;          // read the current value
int? newInt = MyInt.NewValue; // read the newly set value, or null if there is no new value

I want to be able to create fields that are:

  • value types, such as int, bool, etc that have a NOT NULL constraint in the db.
  • value types, such as int?, bool?, etc that allow NULLs in the db.
  • reference types, such as string, that are always nullable.

I'm still learning about Generics, but I'm starting to understand them, however, I'm a bit stuck on this because I can create a Field object as needed above that works with a non-nullable type, or a nullable type, but not both.

This is what I've come up with so far:

protected class Field<T> where T : struct {
    private T _Field;
    private bool _IsNew;

    public Field(T InitialValue) {
        _Field = InitialValue;
        _IsNew = false;
    }

    public T Value {
        get { return _Field; }
        set { 
            if (!_Field.Equals(value)) { 
                _Field = value; 
                _IsNew = true; 
            } 
        }
    }

    public Nullable<T> NewValue {
        get {
            if (_IsNew) {
                _IsNew = false;
                return _Field;
            }
            return null; 
        }
    }

    public bool IsNew { get { return _IsNew; } }

}

This works well. I can also change the type constraint to where T : class, and change Nullable<T> to just T on the NewValue method, which makes it work with nullable types, but how do I get it working with all data types? Do I really need to create two different classes to handle the two different scenarios?

Thanks, Ben

+2  A: 

what prevents from doing what you want? My guess it is the where constraint. If you drop it I do not see what the problem is.

EDIT

You cannot have it both ways. Either you generic class knows something about nulls being assigned to the value, and then you cannot have a value type (like int) as an allowed type, or you do not.

If former is the case your generic class needs the constraint, and you cannot use it for value types like int

In latter case there is no need for the constraint and you still can assign null to the value IF the generic paremeter is of reference type

mfeingold
If I drop the where constraint, then it won't let me return a null for NewValue.
BG100
try default(T) instead of return null
asawyer
then you should exclude private Field<int> MyInt from a list of things you want to cover here
mfeingold
Right, the constraint isn't just preventative. Once you ensure that the type inherits or implements something, your code can rely on this.
Steven Sudit
@mfeingold: I can't, because then someone can just give me the answer of changing it to work with nullable types but not non-nullable types, but I want something that covers both.
BG100
@Steven: Sorry, I don't follow...
BG100
@BG100, in addition to what asawyer pointed out, `new T()` will return 'null' for a nullable type (it's not actually null, but it boxes to null and compares as true with null). For a struct, you are guaranteed to have a parameter-less constructor (which creates the 'empty' struct.) A nullable is a struct, just with special support from the CLR and C# to make it look like it can be null.
Dan Bryant
@BG100: I'll explain. If you say that `T` must implement `IDisposable`, then you can call `Dispose` on an instance of it. If you didn't put that constraint in, the compiler would have no assurances that the call is legal, so it won't be allowed. Does that help?
Steven Sudit
@asawyer: ok, I did look at this, but then if T is "int", I still need NewValue to return a "int?" type with value "null". I'm starting to think that what I want is not possible!
BG100
You cannot have it both ways. Either you generic class knows something about nulls being assigned to the value, and then you cannot have a value type (like int) as an allowed type, or you do not. And if your class does not have the constraint, you still can assign null to the value IF the value is of reference type
mfeingold
@mfeingold: Ok, I guess I need to create two classes to cover both scenarios. Can you paste this comment into your answer? Then I'll accept it!
BG100