views:

406

answers:

2

I'm currently building a node editor (as in Blender) and am having trouble getting delegates to property accessors from a generic type. So far the question here has brought me closest, but I'm having trouble that I think is specifically related to the type of object being generic.

For reference, a "Node" is synonymous with an object, and a "Port" is synonymous with a property.

This is the offending code, which is part of the Node class. The NodePort class is an attribute that may be set on properties to provide details (such as the human-readable name, and data-flow direction) for the property.

public void SetTarget<T>(T Target)
{
    //TODO: Finish clearing old IOs (if any)
    Inputs.Clear();
    Outputs.Clear();

    //Keep track of the current target of this node.
    ThisTarget = Target;

    PropertyInfo[] pinfo = Target.GetType().GetProperties();

    foreach (PropertyInfo property in pinfo)
    {
        Attribute[] attrs = Attribute.GetCustomAttributes(property);
        foreach (Attribute attribute in attrs)
        {
            // If the property has a NodePort attribute, it's specifically requesting to be available as a port on the node.
            if (attribute is NodePort)
            {
                NodePort PortDetails = (NodePort)attribute;

                if (PortDetails.Direction == NodePort.NodePortDirection.PORT_INPUT)
                {
                    // This line throws an ArgumentException, and the only message is "Error binding to target method."
                    NodeInput<T>.SetValue Setter = (NodeInput<T>.SetValue)Delegate.CreateDelegate(typeof(NodeInput<T>.SetValue), (T)Target, property.GetSetMethod());
                    AddInput(Setter, PortDetails.CommonName);
                }
                else if (PortDetails.Direction == NodePort.NodePortDirection.PORT_OUTPUT)
                {
                    // Same exception here.
                    NodeOutput<T>.GetValue Getter = (NodeOutput<T>.GetValue)Delegate.CreateDelegate(typeof(NodeOutput<T>.GetValue), (T)Target, property.GetGetMethod());
                    AddOutput(Getter, PortDetails.CommonName);
                }
            }
        }

    }
}

NodeOutput<T>.GetValue and NodeInput<T>.SetValue are defined as such:

public delegate T GetValue();
public delegate void SetValue(T value);

...in NodeOutput and NodeInput respectively.

Does anyone have any experience with creating delegates for property accessors? Any idea how it may be different when the type in question is generic?

A: 

To create a delegate to the property accessors, you just use GetGetMethod and GetSetMethod; can you explain where it is breaking down?

A simplified example:

using System;
class Foo<T>
{
    public T Value { get; set; }
}
static class Program
{
    static void Main()
    {
        var obj = new Foo<int> { Value = 123 };
        var prop = obj.GetType().GetProperty("Value");
        Func<Foo<int>, int> getter = (Func<Foo<int>, int>)
            Delegate.CreateDelegate(
              typeof(Func<Foo<int>, int>), prop.GetGetMethod());
        int x = getter(obj);
        Console.WriteLine(x);
        Action<Foo<int>, int> setter = (Action<Foo<int>, int>)
            Delegate.CreateDelegate(
              typeof(Action<Foo<int>, int>), prop.GetSetMethod());
        setter(obj, 321);
        Console.WriteLine(obj.Value);
    }
}

Note that to handle things just on object there are tricks involving base-classes etc - or perhaps consider things like HyperDescriptor; similar performance, but much simpler, since you just use PropertyDescriptor and object (after enabling it):

var prop = TypeDescriptor.GetProperties(obj)["Value"];
object val = prop.GetValue(prop);
prop.SetValue(prop, 321);

A last option is Expression; I cover various tricks for property access in Expression in this series.

Marc Gravell
+1  A: 

I think you have a type mismatch here. In the first of your exception lines, setter is declared to be of type NodeInput<T>, which means it is a method that takes T and returns void. But the method that you are assigning to setter is property.GetSetMethod(), which will be a method that takes property.PropertyType and returns void. This will cause an exception unless by good fortune the property.PropertyType is the same as T. Similarly for getter on the second of your exception lines.

I suspect you can't handle this using generics, because you don't know property.PropertyType at compile time, so you can't pass that type as a generic parameter (because generic parameters must be specified at compile time, unless you use type.MakeGenericType).

itowlson
That was supposed to be the purpose of Delegate.CreateDelegate - it accepts a type (typeof(NodeInput<T>.GetSetter)), a target (Target) and a MethodInfo (property.GetSetMethod()). The MethodInfo for GetSetMethod() just describes a simple setter. void Set(T). http://msdn.microsoft.com/en-us/library/system.reflection.propertyinfo.getsetmethod(VS.71).aspx
Sean Edwards
But the MethodInfo for GetSetMethod doesn't match void Set(T). It matches void Set(property.PropertyType). Catch property.GetSetMethod into a temporary variable and examine it in the debugger. For example, suppose T has a property Foo of type int. Then when, in your loop, property is the PropertyInfo for Foo, property.GetSetMethod will be of type int => void, not type T => void, because the Foo property setter takes a int, not a T. The set method is *declared* on T, but does not *accept* a T.
itowlson
Alright I see, yes, you're right. So I guess the question now is, how do I instantiate a generic object off a System.Type? I have the System.Type from property, but I don't know how to use that to create an instance of a generic (in this case my delegate setter).
Sean Edwards
To construct a generic type, you'd use Type.MakeGenericType(). E.g. typeof(NodeInput<>).MakeGenericType(propType). But although this instantiates the type, it still gives you no way of referring to the instance in a strong-typed way *at compile time*. Ultimately, because you don't find out until runtime whether T.Foo is an int or a string, you can't capture this in a compile-time-typed instance of a generic. But consider: could you actually call such a delegate in a strong-typed way anyway? What type of argument would you pass? You may be forced down the weak typing route anyway...
itowlson
Ah hah! After some tinkering, I got it working perfectly. Thank you very much for the tip!
Sean Edwards