开发者

Reflection : How to invoke methods which takes a class object as input

I am using reflection to invoke a method from another dll.That method takes a class object as input. How can i call that method. I tried copying the same class to both dlls and i created an object of that class and passed it. It throws invliad conversion error in complie time itself. Then i tried to let the function take an object as argument and then tried to cast it to the required class. It is throwing invalid cast exception in runtime. this is what i tried

            Test objTest = new Test("name","type");             
            Assembly assembly = Assembly.Load("MyProject.Components");             
            Type dllType = assembly.GetType("MynameSpace.MyClass");
            if (dllType != null)
            {
                MethodInfo m = dllType.GetMethod("MyFunction");
                object objdll;
                objdll = Activator.CreateInstance(dllType);
                object[] args = { objTest };
                if ((m != null))
                {                        
                    strReturnValue += (string)m.Invoke(objd开发者_StackOverflow中文版ll, args);                        
                }
            }

In the second dll the method is like

public string MyFunction(Test obj)

My problem is that Class Test is in the other assembly(the classes are in two different assemblies)


You have a bit of a design problem. You have an assembly (let's call it assembly A) containing the sample reflection code that you posted. You also have a second assembly (let's call it assembly B) that contains MyClass and Test.

The issue is that in your reflection code you are attempting to create an instance of the Test class so that you can pass it as a parameter to MyClass.MyFunction.

You mentioned that you copied the source code for the Test class into assembly A; that will not work. What you've done there is essentially create two different classes with the same name and same structure. Since the two classes are not the same as far as the CLR is concerned, you will get an invalid cast exception if you try to cast one to the other.

Given what you've posted so far, it seems to me that the most straightforward solution for your approach is to have a third assembly (let's call it assembly C) that contains components that are known to both assemblies A and B. Create a class library project in your solution, move the Test class into that project, get rid of any other occurrences of the Test class in the first two projects, and add references in both of the first two projects referencing the new project. Once you've done that, both assembly A and assembly B will be referencing the same class definition for the Test class and the sample code that you've posted will work.

Let me point out something, though. If the code in assembly A doesn't know enough about the code in assembly B in order to instantiate MyClass and call MyFunction directly (rather than through reflection), then how does it know enough about that code to know what parameters to pass? Does MyFunction have a common method signature that assembly A understands? If that's the case, then MyClass should probably implement an interface that assembly A knows about so that assembly A can invoke MyFunction directly, as shown below:

        Assembly assembly = Assembly.Load("MyProject.Components");
        Type dllType = assembly.GetType("MynameSpace.MyClass");
        if (dllType != null)
        {
            IMyInterface instance = Activator.CreateInstance(dllType) as IMyInterface;
            if (instance != null) // check if this object actually implements the IMyInterface interface
            {
                instance.MyFunction(objTest);
            }
        }

If that doesn't seem like an approach that you want, then there are other options. Since it seems like you don't want for assembly A to have a direct reference to assembly B, if you keep the Test class inside of assembly B, then there isn't any way for assembly A to have any knowledge of the Test class in order to construct one. In that case, you could use a factory pattern approach, basically so that assembly A knows of some kind of factory object that is capable of instantiating a Test object. Below is an example of an implementation:

I mentioned above about creating a third project. I would still recommend doing so. In my example, I've named mine "MyProject.Common". It contains the following code:

// define a simple factory interface
public interface IFactory
{
    object CreateInstance();
}

// and a generic one (hey, why not?)
public interface IFactory<T> : IFactory
{
    new T CreateInstance();
}

// define a Factory attribute that will be used to identify the concrete implementation of a factory
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class FactoryAttribute : Attribute
{
    public Type FactoryType { get; set; }

    public FactoryAttribute(Type factoryType)
    {
        this.FactoryType = factoryType;
    }
}

The IFactory interfaces and the Factory attribute will be known and understood by the other projects in my solution, since they both reference the MyProject.Common project.

Below is the code contained within my "MyProject.Components" project:

public class Test
{
    public string Name { get; set; }

    public string Type { get; set; }

    public Test(string name, string type)
    {
        this.Name = name;
        this.Type = type;
    }
}

public class TestFactory : IFactory<Test>
{
    #region IFactory<Test> Members

    public Test CreateInstance()
    {
        return new Test("name", "type");
    }

    #endregion

    #region IFactory Members

    object IFactory.CreateInstance()
    {
        return this.CreateInstance();
    }

    #endregion
}


public class MyClass
{
    // the Factory attribute on the first parameter indicates that the class TestFactory
    // should be used as a factory object to construct the argument for this method
    public string MyFunction([Factory(typeof(TestFactory))]Test obj)
    {
        if (obj == null)
            return null;
        else
            return obj.ToString();
    }
}

Finally, I've replaced the original reflection code that you posted with the following:

        Assembly assembly = Assembly.Load("MyProject.Components");
        Type dllType = assembly.GetType("MynameSpace.MyClass");
        if (dllType != null)
        {
            MethodInfo m = dllType.GetMethod("MyFunction");
            object objdll;
            objdll = Activator.CreateInstance(dllType);

            // use the parameter information to construct the arguments
            ParameterInfo[] parameters = m.GetParameters();
            object[] args;
            if (parameters != null && parameters.Length > 0)
            {
                args = new object[parameters.Length];
                for (int i = 0; i < parameters.Length; i++)
                {
                    // check for factory attributes on the actual parameter
                    FactoryAttribute[] attributes = parameters[i].GetCustomAttributes(typeof(FactoryAttribute), true) as FactoryAttribute[];

                    // if no attributes were found, check the parameter type for factory attributes
                    if (attributes == null || attributes.Length == 0)
                        attributes = parameters[i].ParameterType.GetCustomAttributes(typeof(FactoryAttribute), true) as FactoryAttribute[];

                    // if no attributes were found still, then give up
                    if (attributes == null || attributes.Length == 0)
                    {
                        // this parameter has no factory specified,
                        // so how would this code know how to create the argument for that parameter ???
                        args[i] = null;
                        continue; // move on to the next parameter
                    }

                    // there should only be one factory attribute, so use the first one
                    // assumption made here is that all factory classes will have a parameterless constructor
                    IFactory factory = Activator.CreateInstance(attributes[0].FactoryType) as IFactory;

                    args[i] = factory.CreateInstance();
                }
            }
            else
                // there are no parameters
                args = null;


            if ((m != null))
            {
                strReturnValue += (string)m.Invoke(objdll, args);
            }
        }


If you mean by class object an object that is a Type object then you can just pass the type of the object as the parameter...e.g.

object[] args = {typeof(typeneeded)};

or

object[] args = { assembly.GetType(typeneeded) };


MethodInfo.Invoke() is declared as the following:

public Object Invoke(
    Object obj,
    Object[] parameters
)

The first parameter specifies the object to work on, the second one specifies the parameters for the function.

I replicated your code in LINQPad, and this works fine:

void Main()
{
    string strReturnValue = "";
    Test objTest = new Test("name","type");             
    Type dllType = typeof(MyClass);
    if (dllType != null)
    {
        MethodInfo m = dllType.GetMethod("MyFunction");
        object objdll;
        objdll = Activator.CreateInstance(dllType);
        object[] args = { objTest };
        if ((m != null))
        {                        
            strReturnValue += (string)m.Invoke(objdll, args);                        
        }
    }
}

public class Test
{
    public Test(string s1, string s2)
    {
    }
}

public class MyClass
{
    public string MyFunction(Test t)
    {
        return "";
    }
}

You will have to load the Test object the same way you load the MyClass instance, and as Test requires parameters in the constructor, you'll have to use a ConstructorInfo:

Assembly assembly = Assembly.Load(); //assembly of "Test"
Type testType = assembly.GetType("Test");
ConstructorInfo ci = testType.GetConstructor(
                              BindingFlags.Public | BindingFlags.Instance, 
                              null, 
                              new Type[]{typeof(string), typeof(string)}, 
                              null);
Test objTest = ci.Invoke(new object[] { "name", "type" });

now you can use objTest in the code.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜