|
// <copyright>
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
namespace System.Activities.Expressions
{
using System;
using System.Activities.XamlIntegration;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime;
using System.Xaml;
[TypeConverter(typeof(AssemblyReferenceConverter))]
public class AssemblyReference
{
private const int AssemblyToAssemblyNameCacheInitSize = 100;
private const int AssemblyCacheInitialSize = 100;
// cache for Assembly ==> AssemblyName
// Assembly.GetName() is very very expensive
private static object assemblyToAssemblyNameCacheLock = new object();
// Double-checked locking pattern requires volatile for read/write synchronization
private static volatile Hashtable assemblyToAssemblyNameCache;
// cache for AssemblyName ==> Assembly
// For back-compat with VB (which in turn was roughly emulating XamlSchemaContext)
// we want to cache a given AssemblyName once it's resolved, so it doesn't get re-resolved
// even if a new matching assembly is loaded later.
// Double-checked locking pattern requires volatile for read/write synchronization
private static volatile Hashtable assemblyCache;
private static object assemblyCacheLock = new object();
private Assembly assembly;
private AssemblyName assemblyName;
private bool isImmutable;
public AssemblyReference()
{
}
// This immutable ctor is for the default references, so they can be shared freely
internal AssemblyReference(Assembly assembly, AssemblyName assemblyName)
{
this.assembly = assembly;
this.assemblyName = assemblyName;
this.isImmutable = true;
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Assembly Assembly
{
get
{
return this.assembly;
}
set
{
this.ThrowIfImmutable();
this.assembly = value;
}
}
public AssemblyName AssemblyName
{
get
{
return this.assemblyName;
}
set
{
this.ThrowIfImmutable();
this.assemblyName = value;
}
}
[SuppressMessage(FxCop.Category.Usage, FxCop.Rule.OperatorOverloadsHaveNamedAlternates,
Justification = "A named method provides no advantage over the property setter.")]
public static implicit operator AssemblyReference(Assembly assembly)
{
return new AssemblyReference { Assembly = assembly };
}
[SuppressMessage(FxCop.Category.Usage, FxCop.Rule.OperatorOverloadsHaveNamedAlternates,
Justification = "A named method provides no advantage over the property setter.")]
public static implicit operator AssemblyReference(AssemblyName assemblyName)
{
return new AssemblyReference { AssemblyName = assemblyName };
}
public void LoadAssembly()
{
if (AssemblyName != null && (this.assembly == null || !this.isImmutable))
{
this.assembly = GetAssembly(this.AssemblyName);
}
}
// this code is borrowed from XamlSchemaContext
internal static bool AssemblySatisfiesReference(AssemblyName assemblyName, AssemblyName reference)
{
if (reference.Name != assemblyName.Name)
{
return false;
}
if (reference.Version != null && !reference.Version.Equals(assemblyName.Version))
{
return false;
}
if (reference.CultureInfo != null && !reference.CultureInfo.Equals(assemblyName.CultureInfo))
{
return false;
}
byte[] requiredToken = reference.GetPublicKeyToken();
if (requiredToken != null)
{
byte[] actualToken = assemblyName.GetPublicKeyToken();
if (!AssemblyNameEqualityComparer.IsSameKeyToken(requiredToken, actualToken))
{
return false;
}
}
return true;
}
internal static Assembly GetAssembly(AssemblyName assemblyName)
{
// the following assembly resolution logic
// emulates the Xaml's assembly resolution logic as closely as possible.
// Should Xaml's assembly resolution logic ever change, this code needs update as well.
// please see XamlSchemaContext.ResolveAssembly()
if (assemblyCache == null)
{
lock (assemblyCacheLock)
{
if (assemblyCache == null)
{
assemblyCache = new Hashtable(AssemblyCacheInitialSize, new AssemblyNameEqualityComparer());
}
}
}
Assembly assembly = assemblyCache[assemblyName] as Assembly;
if (assembly != null)
{
return assembly;
}
// search current AppDomain first
// this for-loop part is to ensure that
// loose AssemblyNames get resolved in the same way
// as Xaml would do. that is to find the first match
// found starting from the end of the array of Assemblies
// returned by AppDomain.GetAssemblies()
Assembly[] currentAssemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i = currentAssemblies.Length - 1; i >= 0; i--)
{
Assembly curAsm = currentAssemblies[i];
if (curAsm.IsDynamic)
{
// ignore dynamic assemblies
continue;
}
AssemblyName curAsmName = GetFastAssemblyName(curAsm);
Version curVersion = curAsmName.Version;
CultureInfo curCulture = curAsmName.CultureInfo;
byte[] curKeyToken = curAsmName.GetPublicKeyToken();
Version reqVersion = assemblyName.Version;
CultureInfo reqCulture = assemblyName.CultureInfo;
byte[] reqKeyToken = assemblyName.GetPublicKeyToken();
if ((String.Compare(curAsmName.Name, assemblyName.Name, StringComparison.OrdinalIgnoreCase) == 0) &&
(reqVersion == null || reqVersion.Equals(curVersion)) &&
(reqCulture == null || reqCulture.Equals(curCulture)) &&
(reqKeyToken == null || AssemblyNameEqualityComparer.IsSameKeyToken(reqKeyToken, curKeyToken)))
{
lock (assemblyCacheLock)
{
assemblyCache[assemblyName] = curAsm;
return curAsm;
}
}
}
assembly = LoadAssembly(assemblyName);
if (assembly != null)
{
lock (assemblyCacheLock)
{
assemblyCache[assemblyName] = assembly;
}
}
return assembly;
}
// this gets the cached AssemblyName
// if not found, it caches the Assembly and creates its AssemblyName set it as the value
// we don't cache DynamicAssemblies because they may be collectible and we don't want to root them
internal static AssemblyName GetFastAssemblyName(Assembly assembly)
{
if (assembly.IsDynamic)
{
return new AssemblyName(assembly.FullName);
}
if (assemblyToAssemblyNameCache == null)
{
lock (assemblyToAssemblyNameCacheLock)
{
if (assemblyToAssemblyNameCache == null)
{
assemblyToAssemblyNameCache = new Hashtable(AssemblyToAssemblyNameCacheInitSize);
}
}
}
AssemblyName assemblyName = assemblyToAssemblyNameCache[assembly] as AssemblyName;
if (assemblyName != null)
{
return assemblyName;
}
assemblyName = new AssemblyName(assembly.FullName);
lock (assemblyToAssemblyNameCacheLock)
{
assemblyToAssemblyNameCache[assembly] = assemblyName;
}
return assemblyName;
}
#pragma warning disable 618
[SuppressMessage(
"Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods",
MessageId = "System.Reflection.Assembly.LoadWithPartialName",
Justification = "Assembly.LoadWithPartialName is the only method with the right behavior.")]
private static Assembly LoadAssembly(AssemblyName assemblyName)
{
Assembly loaded = null;
Fx.Assert(assemblyName.Name != null, "AssemblyName.Name cannot be null");
byte[] publicKeyToken = assemblyName.GetPublicKeyToken();
if (assemblyName.Version != null || assemblyName.CultureInfo != null || publicKeyToken != null)
{
// Assembly.Load(string)
try
{
loaded = Assembly.Load(assemblyName.FullName);
}
catch (Exception ex)
{
if (ex is FileNotFoundException ||
ex is FileLoadException ||
(ex is TargetInvocationException &&
(((TargetInvocationException)ex).InnerException is FileNotFoundException ||
((TargetInvocationException)ex).InnerException is FileNotFoundException)))
{
loaded = null;
FxTrace.Exception.AsWarning(ex);
}
else
{
throw;
}
}
}
else
{
// partial assembly name
loaded = Assembly.LoadWithPartialName(assemblyName.FullName);
}
return loaded;
}
#pragma warning restore 618
private void ThrowIfImmutable()
{
if (this.isImmutable)
{
throw FxTrace.Exception.AsError(new NotSupportedException(SR.AssemblyReferenceIsImmutable));
}
}
}
}
|