14

I have a bit of a strange issue here. I have a project constraint where a value of a Property needs to either be a number (int, double, long, etc are all acceptable), a string, or a datetime. The reason that the Value parameter needs to be of one these three (err..well if you count all of the possible numeric value Types allowed it's a bit more) Types is because depending on the type the underlying value will need to be converted to special formats for serialization to a REST API. To simplify things here is a basic idea of the class as a POCO:

class Foo
{
     public string Name {get;set;}
     public Guid Id {get; set;}
     public UNKNOWN Value {get;set;}
}

I thought about using generics for this, with a where T : struct constraint, but this still leaves too many types that can theoretically be set that are actually invalid Types. Sure I can perform type checking and throw exceptions during construction/setting of the Value parameter, but this doesn't feel like "clean" code.

I took a look at this question How do you work with a variable that can be of multiple types? , but it didn't really help since it was more of an issue dealing with inheritance. However, using multiple nullable private fields and returning a single Property based on which one was populated is a possibility, but again I feel there has to be a better way.

The other possibility I was thinking of was to use the dynamic type and and perform some reflection magic to check the underlying type (and perform conversions & formatting/throw exceptions). I'm a bit scared that this will really hurt performance though.

Are there any best practices for this situation? If not, are there any better ways to handle this from what I've mentioned?

Community
  • 1
  • 1
JNYRanger
  • 6,829
  • 12
  • 53
  • 81
  • Don't be scared of `dynamic`. In the end, you won't lose sleep over using it. If you create a test using `dynamic Value` versus `object Value`, use a stop watch to contrast retrieval from a dynamic property versus the object and having to cast to the expected type. Either way, you run the risk of assuming the wrong type is expected. – IAbstract Mar 14 '14 at 22:58
  • @IAbstract I'll definitely try that out, but I'm more concerned about the performance hit that'll be taken from the reflection methods to get the underlying type so I can perform the necessary formatting when it's being serialized. – JNYRanger Mar 14 '14 at 23:07
  • I would *not* use `dynamic` for this task; simply use `Object` if you wish to "do value reflection" (as `dynamic` is suited for "magical late binding of method calls") - However, if at all possible, I'd recommend looking at to unifying along an *interface*. For type conversions, see [Type Converters](http://msdn.microsoft.com/en-us/library/ayybcxe5.aspx), and the various special-casing done in various libraries like Json.NET. – user2864740 Mar 14 '14 at 23:07
  • @user2864740: Agreed; however, an interface is not guaranteed to solve this particular issue. :) – IAbstract Mar 14 '14 at 23:10
  • @JNYRanger My recommendation is alignment along an *interface*, if at all possible. Use `dynamic` for [Duck Typing](http://en.wikipedia.org/wiki/Duck_typing) - use `Object` to accept "an arbitrary type". – user2864740 Mar 14 '14 at 23:10
  • @user2864740 I'm actually using JSON.NET for my serialization. Is there a particular special casting method that you had in mind? – JNYRanger Mar 14 '14 at 23:11
  • @JNYRanger You may be able to use Json.NET Converters? The problem is that this devolves into special-casing (at some level) once leaving unified types. – user2864740 Mar 14 '14 at 23:12

2 Answers2

6

EDIT Eric Lippert taught me this type of dispatch in one of his epic stackoverflow answers, and I'm searching for it at the moment. I will update this answer with a link if/when I track it down (the man has answered quite a few questions). Also, OP, you asked about performance, take a gander at this info also from Lippert: How does having a dynamic variable affect performance?

I would use a mix of dynamic with special case handling, and a generic type catch all for undefined (not yet implemented) types.

class Foo
{
  public dynamic Value { get; set; }
}

class FooHandler
{
  public void Serialize(Foo foo)
  {
    SerializeField(foo.Value);
  }

  void SerializeField(int field)
  {
    Console.WriteLine("handle int");
  }

  void SerializeField<T>(T field)
  {
    throw new NotImplementedException("Serialization not implemented for type: " + typeof(T));
  }
}

class Program
{
  [STAThread]
  static void Main(string[] args)
  {
    Foo f = new Foo();
    f.Value = 1;

    FooHandler handler = new FooHandler();
    handler.Serialize(f);

    Console.ReadKey();
  }
}

And then add types at your leisure.

Community
  • 1
  • 1
payo
  • 4,501
  • 1
  • 24
  • 32
  • 2
    I would still work with `object` on the type level, but this use of `dynamic` (to dynamically late-bind to the correct overload) may be very practical here; +1 for showing such a dispatch. – user2864740 Mar 14 '14 at 23:15
  • 1
    Wow, this did the trick and is extremely elegant in terms of readability. I ended up implementing the handler in an extension method, which is called by a custom JsonConverter (using Json.NET). Didn't need to use any reflection, which is a beautiful thing, or extraneous type checking! If you find that article from Eric Lippert please post it, I'd love to take a look at it. – JNYRanger Mar 15 '14 at 00:25
2

You could use a dedicated class as a "multiple type variable". At instantiation time you can pass an int, double, long, etc. and when you need to get the stored value out you can use a separate call.

public class Foo
{
    public class Value
    {
        object _value;
                
        public Value(int value) { _value = value; }
        public Value(double value) { _value = value; }
        public Value(long value) { _value = value; }
        // etc

        public object GetValue() { return _value; }
    }

    public void TestCall()
    {
        Value myValue = new Value(123);
        Debug.WriteLine(myValue.GetValue());
    }
}
GDavoli
  • 517
  • 4
  • 8
  • I used this for a value that holds either a component or control since they have separate inheritance trees in WinForms. Simple but effective :) – Phillip Sep 06 '20 at 07:35