views:

243

answers:

5

I've got a method that receives an Object[] and then performs actions on that array.

At first I was passing in this array as an IEnumerable<T> however the T can be of two different types.

The T's will always have the same properties, even thought they're different types.

Is it possible to cast to a a type at runtime so that I can used the properties i know each one will contain?

So where as it's possible to do:

var dataObject = (IEnumerable<T>) dataArray;

Is it somehow possible to do:

var dataObject = (dataArray.GetType()) dataArray;
+1  A: 

You can just create it as an interface and have both of those classes implement the interface. Then take Interface[] as the parameter instead of Object and you avoid casting all together.

NickLarsen
+5  A: 

Are you able to modify the source code of the two T types? If so then you could make both of them either (a) inherit from a common base class or (b) implement a common interface.

Edit following comments...

To accomodate different types for the ID property you could use explicit interface implementation. This would allow you to expose the second object's ID as an Int64 when accessed via the common interface. (The ID property would remain accessible as an Int32 when not accessed through the interface.)

Unfortunately the explicit interface implementation will require more changes to your original types.

void YourMethod(IEnumerable<ICommonToBothTypes> sequence)
{
    foreach (var item in sequence)
    {
        Console.WriteLine(item.ID);
    }
}

// ...

public interface ICommonToBothTypes
{
    long ID { get; }
}

public class FirstType : ICommonToBothTypes
{
    public long ID
    {
        get { return long.MaxValue; }
    }
}

public class SecondType : ICommonToBothTypes
{
    // the real Int32 ID
    public int ID
    {
        get { return int.MaxValue; }
    }

    // explicit interface implementation to expose ID as an Int64
    long ICommonToBothTypes.ID
    {
        get { return (long)ID; }
    }
}
LukeH
I beat you to the response, but I like your code so +1. This does avoid the need for boxing doesn't it?
NickLarsen
This looks like the right solution to me. The only issue I've got now is that one of the types has an Id of type Int32? while the other has an Id of Int64? Is there some way I can "not care" about the type when i create the infterface for both to inherit from? I'm guessing not.
Jamie Dixon
You can specify the type of ID with generics. Just change the interface definition to `ICommonToBothTypes<T>` and then declare the ID as `T ID`. Then, the definition of the two classes can pass `int` or `long` as the generic type.
asbjornu
Its going to be a bit of a problem, because you will run into needing to know which type it is again, can you just leave the id off or do you need to use it for this method?
NickLarsen
The only workaround I can think of is to use explicit interface implementation to expose the ID as an `Int64` when accessed through the common interface. I've updated my answer with an example. Hopefully that will do the trick.
LukeH
Thanks guys. Very helpful
Jamie Dixon
+1  A: 

If the two objects in the array derive from the same base type (inheritance) then you can box all the objects in your array as base class objects and you can use them with no knowledge of which particular object it might be.

See here for Boxing and Unboxing information from Microsoft

This is in essence what polymorphism is about, handling objects with a common interface.

Either inheritance from the same base class or creating an interface that both objects support will suit in this case. You will then be able to specifically define your array with either the base class type or the interface type, rather than "object".

NickGPS
+1  A: 

If it's impossible to have the different classes all inherit the same interface, you can get all the properties defined in an object as a collection and then their value through key lookup where the key is the name of the property. I'd implement this as extension methods like so:

public static Dictionary<string, object> GetProperties<T>(this T instance)
   where T : class
{
  var values = new Dictionary<string, object>();
  var properties =
    instance.GetType().GetProperties(BindingFlags.Public   |
                                     BindingFlags.Instance |
                                     BindingFlags.GetProperty);

  foreach (var property in properties)
  {
    var accessors = property.GetAccessors();

    if ((accessors == null) || (accessors.Length == 0))
      continue;

    string key = property.Name;
    object value = property.GetValue(instance, null);

    values.Add(key, value);
  }

  return values;
}

Then you can just do like this to get the property values:

void DoStuff(object[] objects)
{
  foreach (var o in objects)
  {
    var properties = o.GetProperties();
    var value = properties["PropertyName"];
  }
}

It will all be untyped and you'll break things when renaming properties if you forget to also rename the lookup keys in the consuming code, but otherwise, this should work fine. However, the best solution is undoubtedly the one suggested by Luke (using interfaces).

asbjornu
+1  A: 

If you have access to the sourcecode defining the two element types

You should introduce a new interace containing the common properties and let the other two interfaces inherit from your new base interface. In your method you then use IEnumerable.Cast(TResult) to cast all elements to the common interface:

var dataObject = dataArray as IEnumerable;
if (dataObject != null)
{
  var data = dataObject.Cast<ICommonBase>()
  // do something with data
}

If you can't modify the sourcecode defining the two element types:

Use the IEnumerable.OfType() to filter elements based on their known types, effectively creating two strongly typed collections. Next perform the same operations with each of them (most verbose approach).

var dataObject = dataArray as IEnumerable;
if (dataObject != null)
{
  var a = dataObject.OfType<InterfaceA>()
  // do something with a

  var b = dataObject.OfType<InterfaceB>()
  // do the same with b
}

A reference of the functions used can be found on msdn.

If you want to avoid code duplication your only chance will be to use something called duck typing.Phil Haack has a great article on it. The idea is to introduce a new interface that contains the common elements. You then duck-type the array elements, effectively making them comply to the new interface at runtime.

The last option would be using reflection and extracting property infos by common names and then access each elements data. This is the slowest and IMHO most cumbersome approach.

Johannes Rudolph