views:

374

answers:

3

I have a third-party, closed source application that exports a COM interface, which I am using in my C#.NET application through Interop. This COM interface exports many objects that all show up as System.Object until I cast them to the appropriate interface type. I want to assign an property of all of these objects. Thus:

foreach (object x in BigComInterface.Chickens)
{
    (x as Chicken).attribute = value;
}
foreach (object x in BigComInterface.Ducks)
{
    (x as Duck).attribute = value;
}

But assigning the property is likely (for application-specific reasons that are unavoidable) to throw Exceptions from which I want to recover, so I really want a try/catch around each one. Thus:

foreach (object x in BigComInterface.Chickens)
{
    try
    {
        (x as Chicken).attribute = value;
    }
    catch (Exception ex)
    {
        // handle...
    }
}
foreach (object x in BigComInterface.Ducks)
{
    try
    {
        (x as Duck).attribute = value;
    }
    catch (Exception ex)
    {
        // handle...
    }
}

Obviously, it would be so much cleaner to do this:

foreach (object x in BigComInterface.Chickens)
{
    SetAttribute<Chicken>(x as Chicken, value);
}
foreach (object x in BigComInterface.Ducks)
{
    SetAttribute<Duck>(x as Duck, value);
}

void SetAttribute<T>(T x, System.Object value)
{
    try
    {
        x.attribute = value;
    }
    catch
    {
        // handle...
    }
}

See the problem? My x value can be of any type, so the compiler can't resolve .attribute. Chicken and Duck are not in any kind of inheritance tree and they do not share an interface that has .attribute. If they did, I could put a constraint for that interface on T. But since the class is closed-source, that's not possible for me.

What I want, in my fantasy, is something like a constraint requiring the argument to have the .attribute property regardless of whether it implements a given interface. To wit,

void SetAttribute<T>(T x, System.Object value) where T:hasproperty(attribute)

I'm not sure what to do from here other than to cut/paste this little try/catch block for each of Chicken, Duck, Cow, Sheep, and so on.

My question is: What is a good workaround for this problem of wanting to invoke a specific property on an object when the interface that implements that property cannot be known at compile time?

+2  A: 

Unfortunately the only way to do this is to constrain the type parameter with an interface that defines that property and is implemented on all types.

Since you do not have the source this will be impossible to implement and as such you will have to use some sort of workaround. C# is statically typed and as such doesn't support the kind of duck-typing you want to use here. The best thing coming soon (in C# 4) would be to type the object as dynamic and resolve the property calls at execution time (note that this approach would also not be generic as you cannot constrain a generic type parameter as dynamic).

Andrew Hare
You can do this via reflection.
Reed Copsey
@Reed - Good point!
Andrew Hare
With dynamic, you don't need the generic parameter. Just use a single method that takes the value as dynamic, and try setting obj.attribute. It should work fine, provided "attribute" exists as a property on obj.
Reed Copsey
Right - that is what I was thinking as well but I wanted to make it clear that all static type checking would be gone :)
Andrew Hare
+3  A: 

Unfortunately, this is tricky currently. In C# 4, the dynamic type may help quite a bit with this. COM interop is one of the places that dynamic really shines.

However, in the meantime, the only option that allows you to have any type of object, with no restrictions on interfaces, would be to revert to using reflection.

You can use reflection to find the "attribute" property, and set it's value at runtime.

Reed Copsey
Thank you, Reed. I did try the Reflection technique--it does about what I need but is kind of slow.
catfood
Since there's no common interface, there's no way to do the fast vtable-based generic invoke - you have to do dynamic lookup on name, and this is always slow, Reflection or not.
Pavel Minaev
@Pavel: Yes, it's always slow, but technically, it's the only way that works without changing the existing classes, until C# 4 is out. I'm not saying that its good, but rather that it would work.
Reed Copsey
+2  A: 

Well, depending on how humongous your exception handling code is (and if i am not mistaken it could be quite so) using the following trick might help you:

class Chicken
{
 public string attribute { get; set; }
}

class Duck
{
 public string attribute { get; set; }
}

interface IHasAttribute
{
 string attribute { get; set; }
}

class ChickenWrapper : IHasAttribute
{
 private Chicken chick = null;
 public string attribute
 {
  get { return chick.attribute; }
  set { chick.attribute = value; }
 }
 public ChickenWrapper(object chick)
 {
  this.chick = chick as Chicken;
 }
}

class DuckWrapper : IHasAttribute
{
 private Duck duck = null;
 public string attribute
 {
  get { return duck.attribute; }
  set { duck.attribute = value; }
 }
 public DuckWrapper(object duck)
 {
  this.duck = duck as Duck;
 }
}

void SetAttribute<T>(T x, string value) where T : IHasAttribute
{
 try
 {
  x.attribute = value;
 }
 catch
 {
  // handle...
 }
}
dionadar
This is ultimately the best solution. Wrapper classes allow you to take third party code and apply your own inheritance relationships etc. It will also insulate you from changes to the 3rd party API.
plinth
This is a good solution in general, but my original problem is that a third-party closed-source application is handing me Chickens and Ducks from COM via Interop, wrapped in System.Objects. I could wrap them in ChickenWrappers and DuckWrappers as you suggest, but to do that I'd have to go through *all* the available objects, attempt to cast to Duck, Chicken, Swan, Goose, etc., and then wrap the result in DuckWrapper, ChickenWrapper, SwanWrapper, or GooseWrapper, etc.That would be one horrible switch statement.
catfood
Actually, look at the Wrapper constructor: It takes an object and does the casting there, so you would only do "new ChickenWrapper(o)" instead of "(Chicken)o"
dionadar