Создание службы WCF путем определения типа во время выполнения

Я пытаюсь создать службу WCF, не зная ее типа/интерфейса во время выполнения. Для этого я использую ChannelFactory. ChannelFactory — это универсальный класс, поэтому мне нужно использовать Type.MakeGenericType. Тип, который я передаю в MakeGenericType, берется из списка интерфейсов, который я ранее собрал с помощью отражения путем поиска в некоторых сборках.

В конце концов, я вызываю MethodInfo.Invoke для создания объекта. Объект создается просто отлично, но я не могу привести его к нужному интерфейсу. При кастинге я получаю следующую ошибку:

"Невозможно преобразовать прозрачный прокси в тип Tssc.Services.MyType.IMyType"

После некоторых экспериментов я обнаружил, что проблема заключается в интерфейсе/типе, переданном в MakeGenericType. Если я подставлю интерфейс в своем списке на фактический интерфейс, то все работает нормально. Я прочесал два объекта и не вижу разницы. Когда я изменяю код для создания обоих типов, сравнение их с Equals возвращает false. Мне неясно, просто ли Equals проверяет, что они ссылаются на один и тот же объект (нет), или они проверяют все свойства и т. д.

Может ли это быть как-то связано с тем, как я собрал свои интерфейсы (отражение, сохранение в списке...)? Сравнение объектов, кажется, указывает на то, что они эквивалентны. Я напечатал все свойства для обоих объектов, и они одинаковы. Нужно ли копать глубже? Если да, то куда?

         // createService() method

     //*** tried both of these interfaces, only 2nd works - but they seem to be identical
     //Type t = interfaces[i]; // get type from list created above - doesn't work
     Type t = typeof(Tssc.Services.MyType.IMyType); // actual type - works OK

     // create ChannelFactory type with my type parameter (t)
     Type factoryType = typeof(ChannelFactory<>);
     factoryType = factoryType.MakeGenericType(new Type[] { t });         

     // create ChannelFactory<> object with two-param ctor
     BasicHttpBinding binding = new BasicHttpBinding();
     string address = "blah blah blah";
     var factory = Activator.CreateInstance(factoryType, new object[] { binding, address });

     // get overload of ChannelFactory<>.CreateChannel with no parameters
     MethodInfo method = factoryType.GetMethod("CreateChannel", new Type[] { });

     return method.Invoke(factory, null);

     //--------------- code that calls code above and uses its return

     object ob = createService();

     //*** this cast fails
     Tssc.Services.MyType.IMyType service = (Tssc.Services.MyType.IMyType)ob;

person    schedule 19.02.2013    source источник
comment
Было бы полезно получить t.ToString() (после t = interfaces[i]) и ob.GetType().ToString() (после object ob = createService()) — это может дать нам понять, почему это не работает.   -  person Matt Whetton    schedule 20.02.2013
comment
Пробовал, но ToString мало что мне сказал. Я изучил содержимое рабочей (жесткий код) и нерабочей (из интерфейсов []) версий 't' и обнаружил, что единственное различие заключается в свойстве TypeHandle.   -  person DaveK    schedule 20.02.2013
comment
Два интерфейса должны быть одинаковыми - строка to должна давать вам пространство имен и имя типа точно такого же интерфейса. Если они не одинаковы, это не сработает.   -  person Matt Whetton    schedule 20.02.2013
comment
ToString действительно дает пространство имен/тип. Они одинаковые. Поэтому я сбросил оставшиеся свойства обеих версий типа и обнаружил, что единственное отличие — это свойство TypeHandle. Что может привести к тому, что TypeHandle будет другим, если все остальное останется прежним?   -  person DaveK    schedule 20.02.2013
comment
Я думаю, что TypeHandle всегда будет другим — это уникальная внутренняя точка структуры данных. Что произойдет, если вместо Type t = interfaces[i] вы попробуете Type t = Type.GetType(interfaces[i].ToString())   -  person Matt Whetton    schedule 20.02.2013
comment
Type.GetType возвращает значение null.   -  person DaveK    schedule 20.02.2013
comment
Это действительно странно! Как вы заполняете свой массив интерфейсов — это происходит в том же AppDomain?   -  person Matt Whetton    schedule 20.02.2013
comment
Извините, я не понимаю AppDomains. Я заполняю свой список (как описано ниже) отдельным методом в том же консольном приложении. Я использую System.Reflection.Assembly.LoadFile для поиска сборок в известной папке. В каждой сборке я ищу интерфейсы с сервисным атрибутом System.ServiceModel.ServiceContractAttribute. Я сохраняю эти интерфейсы (типы) в списке (interfaces[]).   -  person DaveK    schedule 20.02.2013
comment
Хорошо, это имеет смысл — и эти сборки не упоминаются в вашем консольном приложении?   -  person Matt Whetton    schedule 20.02.2013
comment
Правильный. Дело в том, чтобы обнаружить их во время выполнения. В общем, общая цель состоит в том, чтобы иметь возможность добавлять/удалять сервисы без постоянного обновления какого-то мастер-списка, жестко запрограммированных типов... Следовательно, все типы создания во время выполнения и т.д.   -  person DaveK    schedule 20.02.2013
comment
Итак, в той части, где вы выполняете приведение (Tssc.Services.MyType.IMyType service = (Tssc.Services.MyType.IMyType)ob;), где находится Tssc.Services.MyType.IMyType?   -  person Matt Whetton    schedule 20.02.2013
comment
Ах. Код, который считывает сборки, объединяет типы и создает канал службы, находится в отдельной сборке (Utils) из консольного приложения. Наряду с Utils консольное приложение имеет ссылку на еще одну сборку (Support), содержащую рассматриваемый интерфейс (IMyType). Актерский состав находится в консольном приложении.   -  person DaveK    schedule 20.02.2013


Ответы (1)


Хорошо, я понимаю, что здесь происходит - проблема связана с загрузкой одной и той же сборки, которая эффективно загружается дважды - один раз по ссылке и один раз с помощью команды загрузки сборки. Что вам нужно сделать, так это изменить место, где вы загружаете свою сборку, и проверить, существует ли она уже в текущем AppDomain, например:

Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name.Equals("ClassLibrary1Name"));

if (assembly == null)
{
    assembly = System.Reflection.Assembly.LoadFile("path to your assembly");
}

//do your work here

Таким образом, если сборка уже загружена в память, она будет использовать ее.

person Matt Whetton    schedule 20.02.2013
comment
Проблема не совсем такая, как вы описали, но она указала мне правильное направление. Моя проблема в том, что я загружаю две версии одного и того же файла. Один файл находится в моей универсальной папке, а другой — в папке консольного приложения. Во время разработки я копировал файлы для тестирования. Кажется, теперь я должен собраться. Я думаю, что мне нужно начать использовать GAC и приблизиться к моей возможной развернутой конфигурации. Мэтт, я очень ценю ваше время и усилия. - person DaveK; 20.02.2013