2012-05-07 91 views
8

將其類型謂詞請閱讀整個問題。我有一個獨特的情況,有幾個我想解決的限制。加載一個組件,而在另一個應用程序域

在我的代碼有被編譯成Predicate<System.Type>表達式樹。我的目標是加載程序集沒有鎖定它(這是該項目的輸出組件,被不斷地重修),適用其類型的列表在這個謂語並獲得結果類型的回列表

// this is what I need: 
return assembly.GetTypes().Where(t => predicate(t)).Select(t => t.FullName); 

該程序集應該加載到另一個應用程序域中,因爲我希望一收到我需要的信息就立即卸載它。

這是它得到棘手。有幾個我正面臨的問題:

如果我在另一個appdomain中加載程序集並簡單地返回它的所有類型的數組,以便我可以將謂詞應用回到我的主appdomain中,只要類型被編組回到我的主要應用程序域我得到一個FileNotFoundException,指出該組件是找不到的。這使得sence,因爲程序集只被加載到我創建的另一個appdomain中。在主應用程序域中加載它也會失敗。

如果或者我嘗試將謂詞傳遞給另一個appdomain並將其應用於其中並返回一個字符串數組(完整類型名稱),我得到一個SerializationException: "Cannot serialize delegates over unmanaged function pointers, dynamic methods or methods outside the delegate creator's assembly.",因爲謂詞是一個動態方法(已編譯從表達式樹中)。

將其加載到主應用程序域中將不會有這些問題,但由於無法卸載已加載的程序集而無需卸載整個應用程序域,只要程序集發生更改(重建後),任何嘗試加載程序集具有相同的名稱將導致例外。

語境:
我建立ReSharper的一個插件叫做Agent Mulder。插件背後的想法是分析解決方案中的DI/IoC容器註冊,並幫助ReSharper找出通過DI容器註冊的類型的使用情況(您可以觀看它的工作原理的簡短視頻here)。

大多數情況下,分析容器註冊非常簡單 - 我只需檢測足夠的信息即可知道哪些具體類型受到影響。在Castle Windsor的這個例子中:Component.For<IFoo>().ImplementedBy<Foo>()由此產生的類型是顯而易見的,AllTypes.FromThisAssembly().BasedOn<IFoo>()也是如此 - 給我提供足夠的信息來對這條線會影響到的具體類型提供猜測。然而,考慮這個註冊在溫莎城堡:

container.Register(Classes 
    .FromAssemblyInDirectory(new AssemblyFilter(".").FilterByName(an => an.Name.StartsWith("Ploeh.Samples.Booking"))) 
    .Where(t => !(t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dispatcher<>))) 
    .WithServiceAllInterfaces()); 

source

這裏的信息取決於只會在運行時進行評估的謂詞。

因爲我所能做的就是靜態分析這一點,所以我手裏拿着來自Where子句的lambda表達式的ReSharper AST(在ReSharper中稱爲PSI)表示。我可以將這個AST轉換成一個LINQ表達式樹,然後將它編譯成一個委託。

我的想法是是加載通過反射輸出組件(由FromAssembly*描述符確定),並且爲了獲得類型名(我只需要姓名)在組裝的類型應用此委託。每次裝配改變時都必須重新評估(我不關心性能的這一點)。總之,除非有人能夠推薦一種更好的方式來確定受謂詞影響的類型,我想知道如何用反射來做到這一點(不幸的是我沒有考慮過其他元數據讀者,因爲我會必須以某種方式將lambda表達式AST轉換爲不同數據類型的謂詞,並且我不知道是否存在1對1轉換)。

謝謝您的閱讀。這個問題在可用時將有500點獎勵。

+0

如果'Expression'是序列化,你可以創建'Predicate'委託作爲一個'Expression',並將其傳遞給appdomain邊界,然後在'remote'appdomain中進行編譯。但可悲的是,那不是那種情況:( – leppie

+0

@leppie是的,我想過序列化表達式並傳遞它,你是對的,它並不像聽起來那麼簡單... –

回答

2

您需要加載程序集才能獲得Type實例,因此單獨的AppDomain似乎是正確的解決方案。

因此,您需要將謂詞Expression轉換爲AppDomain,這意味着您必須對其進行序列化/反序列化。

由於各種原因,此要求越來越頻繁。我在看這個,因爲我想通過WCF服務將Linq轉換爲Entities表達式。

幸運的是,有一些現有的實現。

我發現這一個:CodePlex - Expression Tree Serializer


我剛剛與Types測試它,這一點也適用:

Expression<Func<Type,bool>> predicate = 
    t => (!t.IsGenericType && t.Name == "Int32"); 

var s = new ExpressionSerialization.ExpressionSerializer(); 
var xml = s.Serialize(predicate); 

var p = s.Deserialize(xml) as Expression<Func<Type, bool>>; 
var f = p.Compile(); 

Console.WriteLine("Int32: " + f(typeof(int))); // true 
Console.WriteLine("String: " + f(typeof(string))); // false 
+0

謝謝!經過額外的雜技(XElement本身並沒有標記'[Serializable]',這就打破了appdomains之間的轉換),我通過轉換/從一個字符串來解決它。對於簡單的表達式,這*看起來*做這項工作 - 我將不得不嘗試更復雜的。如果這個工程,你的答案可能是:) –

+0

不客氣。那個圖書館爲我節省了很多工作。如果您發現任何它無法處理的情況,請讓我知道。 –

1

讓包裹謂詞委託實例與MBR對象,Type類型的參數將從其他領域編組得很好:

class RemotablePredicate<T> : MarshalByRefObject 
{ 
    readonly Predicate<T> predicate; 
    public RemotablePredicate(Predicate<T> predicate) { this.predicate = predicate; } 
    public bool Accepts(T arg) { return predicate(arg); } 
} 

建立某種類型的加載,探索組裝並將結果返回給主域名:

class AssemblyExplorer : MarshalByRefObject 
{ 
    public string[] GetTypesByPredicate(
    string assemblyPath, RemotablePredicate<Type> predicate) 
    { 
    // MS reflection api reqires all dependencies here 
    var bytes = File.ReadAllBytes(assemblyPath); 
    var assembly = Assembly.ReflectionOnlyLoad(bytes); 

    var types = new List<string>(); 
    foreach (var type in assembly.GetTypes()) 
     if (predicate.Accepts(type)) 
     types.Add(type.FullName); 

    return types.ToArray(); 
    } 
} 

讓所有的工作:

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Linq.Expressions; 
using System.Reflection; 

class Program 
{ 
    static void Main() 
    { 
    var fooDomain = AppDomain.CreateDomain("Foo"); 

    Expression<Predicate<Type>> expr = t => t.IsValueType; 
    var compiledPredicate = expr.Compile(); 
    var remotablePredicate = new RemotablePredicate<Type>(compiledPredicate); 

    var explorerType = typeof(AssemblyExplorer); 
    var explorerInstance = (AssemblyExplorer) fooDomain 
     .CreateInstanceAndUnwrap(explorerType.Assembly.FullName, explorerType.FullName); 

    var types = explorerInstance.GetTypesByPredicate(
     "JetBrains.Annotations.dll", remotablePredicate); 

    Console.WriteLine("Matched types: {0}", types.Length); 
    foreach (var type in types) Console.WriteLine(" {0}", type); 
    } 
} 
相關問題