Отражение: как вызывать методы, которые принимают объект класса в качестве ввода - PullRequest
3 голосов
/ 07 сентября 2010

Я использую отражение для вызова метода из другого dll. Этот метод принимает объект класса в качестве входных данных.Как я могу назвать этот метод.Я попытался скопировать один и тот же класс в обе библиотеки DLL, и я создал объект этого класса и передал его.Выдает ошибку преобразования invliad в само время выполнения.Затем я попытался позволить функции принять объект в качестве аргумента, а затем попытался привести его к требуемому классу.Выдает недопустимое исключение приведения во время выполнения.вот что я пробовалдве разные сборки)

Ответы [ 3 ]

1 голос
/ 08 сентября 2010

У вас небольшая проблема с дизайном.У вас есть сборка (назовем ее сборкой A), содержащая пример кода отражения, который вы опубликовали.У вас также есть вторая сборка (назовем ее сборкой B), которая содержит MyClass и Test.

Проблема в том, что в вашем коде отражения вы пытаетесь создать экземпляр класса Test, чтобы вы могли передать егов качестве параметра MyClass.MyFunction.

Вы упомянули, что скопировали исходный код класса Test в сборку A;это не будет работать.По сути, вы создали два разных класса с одинаковыми именами и одинаковой структурой.Поскольку эти два класса не совпадают в том, что касается CLR, вы получите недопустимое исключение приведения, если попытаетесь привести один к другому.

Учитывая то, что вы опубликовали, кажется,Для меня самое простое решение для вашего подхода - это иметь третью сборку (назовем ее сборкой C), которая содержит компоненты, которые известны обеим сборкам A и B. Создайте проект библиотеки классов в своем решении, переместите класс Test вэтот проект, избавьтесь от любых других вхождений класса Test в первых двух проектах и ​​добавьте ссылки в оба первых проекта, ссылающиеся на новый проект.Как только вы это сделаете, сборка A и сборка B будут ссылаться на одно и то же определение класса для класса Test, и пример кода, который вы опубликовали, будет работать.

Однако позвольте мне кое-что указать.Если код в сборке A не знает достаточно о коде в сборке B, чтобы создать экземпляр MyClass и вызвать MyFunction напрямую (а не через отражение), то как он узнает достаточно об этом коде, чтобы знать, какие параметры передать?Есть ли у MyFunction общая сигнатура метода, которую понимает сборка A?В этом случае MyClass, вероятно, должен реализовать интерфейс, о котором знает сборка A, чтобы сборка A могла напрямую вызывать MyFunction, как показано ниже:

        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);
            }
        }

Если это не похоже на подход, который выхочу, то есть другие варианты.Так как кажется, что вы не хотите, чтобы сборка A имела прямую ссылку на сборку B, если вы сохраняете класс Test внутри сборки B, то сборка A не может иметь никаких знаний о классе Test.для того, чтобы построить один.В этом случае вы могли бы использовать подход фабричного шаблона, в основном так, чтобы сборка A знала о каком-то фабричном объекте, который способен создавать экземпляр объекта Test.Ниже приведен пример реализации:

Я упоминал выше о создании третьего проекта.Я все еще рекомендовал бы сделать это.В моем примере я назвал мой "MyProject.Common".Он содержит следующий код:

// 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;
    }
}

Интерфейсы IFactory и атрибут Factory будут известны и поняты другим проектам в моем решении, поскольку они оба ссылаются на проект MyProject.Common.

Ниже приведен код, содержащийся в моем проекте «MyProject.Components»:

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();
    }
}

Наконец, я заменил исходный код отражения, который вы опубликовали, следующим текстом:

        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);
            }
        }
0 голосов
/ 07 сентября 2010

MethodInfo.Invoke() объявляется следующим образом:

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

Первый параметр указывает объект для работы, второй - параметры функции.

Я реплицировал вашкод в LINQPad, и это прекрасно работает:

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 "";
    }
}

Вам нужно будет загрузить объект Test так же, как вы загружаете экземпляр MyClass, а так как Test требует параметры в конструкторе, вам придется использовать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" });

теперь вы можете использовать objTest в коде.

0 голосов
/ 07 сентября 2010

Если вы подразумеваете под объектом класса объект, который является Type объектом, то вы можете просто передать тип объекта в качестве параметра ... например.

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

или

object[] args = { assembly.GetType(typeneeded) };
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...