views:

79

answers:

2

I have an object that has properties of another object and one called DataValue, but the type that I want DataValue to return depends on information contained in the object in the other property. I'm not convinced my way is the best way to do this.

I have this business object called an AssetStructure.

An AssetStructure object contains a generic list of IAssetStructureField objects, which are a series of objects that basically hold information about the data that can be held in that field, a default value of a certain datatype and some displaying information properties. Each of the objects implementing the IAssetStructureField interface will hold different datatype. For example, one's DefaultValue's type maybe string and the other maybe a List<ofCustomType>.

I have my Asset object containing a generic list of objects called AssetDataField. The AssetDataField has properties of one containing the AssetStructureField and one called DataValue, the Asset's data for that StructureField.

My problem is datatype of AssetDataField DataValue property, it will need to be different depending on the details of the AssetStructureField object. This StructureField may hold data representing all the user groups with access to the Asset (datatype List<UserGroups>), and another might just be a description field (datatype string), so I need the DataValue coming out of the AssetDataField to be of the same type.

What I'm thinking of doing now, and that I feel can probably be done much better, is having the AssetDataField.DataValue return an object, and then cast it to the typeof the AssetDataField.StructureField.DefaultValue.

object fieldValue;
object fieldDefaultValue;
Asset certainAsset = new Asset(32423);
foreach (AssetDataField dataField in certainAsset.DataFields)
{
     fieldDefaultValue = datafield.StructureField.DefaultValue;
     fieldValue = datafield.DataValue as typeof(fieldDefaultValue);

     // then do stuff depending on what typeof(fieldValue) is.  This is where I
     // see things getting particularly ugly.  Not only just because that
     // this class here will need to know the possible types that may be 
     // returned, so it can deal.

     if (typeof(fieldValue) == whatever)
     {
          // deal;
     }
     else if (typeof(fieldValue) == whatever2)
     {
          // deal differently;
     }
}

Does anyone have any suggestions? I am not a opposed, at all, to a complete redo. I'm really sorry this is so long-winded, I just wanted to try and explain the situation well. I tried to put together a UML diagram to help out, but my ArgoUML was acting up. Thanks for any insights at all that you can provide.

A: 

It seems like you should make AssetDataField a possibly abstract base class, and derive other classes from it to perform the work. For example:

class Program
{
    static void Main(string[] args)
    {
        Asset certainAsset = new Asset(32423);
        foreach (AssetDataField dataField in certainAsset.DataFields)
        {
            dataField.Deal();
        }
    }
}

class Asset
{
    public List<AssetDataField> DataFields = new List<AssetDataField>();
    public Asset(int id)
    {
        // Load asset #id
        if (id == 32423)
        {
            DataFields.Add(new AssetDataFieldString());
            DataFields.Add(new AssetDataFieldFloat());
        }
    }
}

abstract class AssetDataField
{
    public AssetDataField()
    {
        FieldValue = DefaultValue;
    }

    public abstract object DefaultValue { get; }
    public abstract object FieldValue { get; set; }
    public abstract void Deal();
}

abstract class AssetDataFieldType<T> : AssetDataField
{
    protected T internalValue;
    public override object FieldValue
    {
        get
        {
            return TypedValue;
        }
        set
        {
            TypedValue = (T)System.Convert.ChangeType(value, typeof(T));
        }
    }

    public virtual T TypedValue
    {
        get
        {
            return internalValue;
        }
        set
        {
            internalValue = value;
        }
    }
}

class AssetDataFieldString : AssetDataFieldType<string>
{
    public override object DefaultValue
    {
        get { return "Default String"; }
    }

    // Optionally override TypedValue

    public override void Deal()
    {
        Console.WriteLine(TypedValue.PadLeft(20));
    }
}

class AssetDataFieldFloat : AssetDataFieldType<float>
{
    public override object DefaultValue
    {
        get { return 0; }
    }

    // Optionally override TypedValue

    public override void Deal()
    {
        Console.WriteLine(TypedValue.ToString("#0.000"));
    }
}
BlueMonkMN
A: 

Note: this smells like the result of querying an EAV based system. In the same way that meta data is the backbone of this sort of system the code referencing it should strive to know what it is accessing (and thus the types) at compile time. That said if you want to simply display the data this sort of thing is required no matter what.

C# is statically typed so you cannot put 'different things' into the same 'slot' (variable, array location) unless the slot is the right 'shape' to take all of them(1). The only slot currently available in c# for this is object. This will work but will box any value types(2).

In c# 4.0 you can use dynamic, which under the hood will be an object but at least will let you invoke any methods on it you want even if the compiler doesn't think it's legal via object.

If all the types in question share a common interface then you can avoid object and get some useful semantics (say if double Sum(double d) was a meaningful operation for any instance you were dealing with then this could yield useful results. However it sounds like you do not control the types present (and thus stand no chance of getting them to conform to useful interfaces).

If the set of possible types is tractable the technique described below can work but it is still cumbersome.

// boxes if needed
public interface IGeneralValue
{
    object Value { get; }
    Type GetValueType();
}

public class Value<T> : IGeneralValue
{
     public T Value { get; set;}
     object IGeneralValue.Value 
     {
         get { return (object)this.Value; }
     } 

     public Type GetValueType()
     {
         return typeof(T);
     }
}

Then you can stay statically typed where possible but if not something similar to your previous code will work.

Asset certainAsset = new Asset(32423);
foreach (IGeneralValue dataField in certainAsset.DataFields)
{
    object fieldValue = datafield.Value;
    Type fieldType = dataField.GetValueType();     

    if (typeof(double).Equals(fieldType))
    {
        double d = ((double)fieldValue);
    }
    else if (typeof(string).Equals(fieldType))
    {
        string d = ((string)fieldValue);
    }
    else if (typeof(whatever).Equals(fieldType))
    {
        // deal with whatever
    }
    else
    {
        // the safe option
        throw new NotSupportedException(fieldType +" is not supported!");
    }
}
  1. Without unsafe code or unions (only structs) at least.
  2. This has implications not just on performance, you cannot unbox an int as a double for example, despite that conversion working on unboxed instances.
ShuggyCoUk