从GUID获取类型

13

由于各种原因,我需要在C#中实现一种类型缓存机制。幸运的是,CLR提供了Type.GUID来唯一标识类型。不幸的是,我找不到任何根据此GUID查找类型的方法。有Type.GetTypeFromCLSID(),但根据我的文档理解(和实验),它执行非常不同的操作。

除了遍历所有已加载的类型并比较它们的GUID之外,是否有其他方法可以根据其GUID获取类型?

编辑:我忘记提到我真的想要固定宽度的“类型指纹”,这就是为什么GUID对我如此吸引的原因。在一般情况下,当然可以使用类型的完全限定名称。


1
这也可能很有用:http://ayende.com/Blog/archive/2008/01/12/System.Type.GUID-stability.aspx - Rubens Farias
不错!但是如果您可以保证类型名称的唯一性,您可以使用 Type.FullName(不包含程序集信息)作为其更小的表示形式。 - Rubens Farias
潜水:是的,我考虑过了。不过问题在于,如果我生成了任意映射,我还必须将映射持久化到磁盘上,而我希望避免这种情况(我承认,这是出于纯粹的懒惰)。 - Tamas Czinege
@Peter Wone:在启动时构建也有其明显的缺点。例如,如果进程在启动后动态加载另一个程序集,则新加载的程序集中定义的类型不会被编目录。 - Tamas Czinege
2
缓存还是不缓存:这是一个问题;是在心智中忍受长时间的获取时间,还是构建一个缓存,在组合过程中失去一些,这是值得考虑的。 - Peter Wone
显示剩余9条评论
7个回答

6

为什么不使用指定的属性,即AssemblyQualifiedName?该属性被记录为“可以保存并稍后用于加载类型”。

GUID用于COM互操作。


1
谢谢,但正如我在其中一条评论中提到的那样,理想情况下,我希望有一个固定宽度的“指纹”,处理可变长度的字符串会让我的生活变得更加复杂。但是,如果您没有这样的要求,那么我同意,完全限定名称是正确的选择。 - Tamas Czinege
2
@文档:如果您创建超过2^128种类型,会怎样呢? - Anon.
5
匿名用户说:“这不可能发生 :) 在全世界所有计算机共同创造2^128种类型之前,宇宙将会崩溃。” - Tamas Czinege
1
可变长度字段对于像SQLite这样的进程内关系引擎来说非常快。 - Remus Rusanu
2
Remus Rusanu:我真的不想深入讨论细节,因为它们超出了范围,但我有非常好的理由使用我所使用的数据结构,并且我通常知道自己在做什么(至少我希望如此)。开发时间在这里是次要的考虑因素。我很乐意谈论它,因为它确实是非常有趣的东西,但不幸的是,我不认为这个问题是适当的媒介。感谢您的建议,一般来说,您是正确的,现成的东西大多数时候最有用。 - Tamas Czinege
显示剩余4条评论

5
这可能只是之前已经发布的答案的总结,但我认为在构建 Guid->类型的映射表之前,没有办法做到这一点。
我们在框架初始化时完成此操作:
static TypeManager()
    {
        AppDomain.CurrentDomain.AssemblyLoad += (s, e) =>
        {
            _ScanAssembly(e.LoadedAssembly);
        };

        foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
        {
            _ScanAssembly(a);
        }
    }

    private static void _ScanAssembly(Assembly a)
    {
        foreach (Type t in a.GetTypes())
        {
           //optional check to filter types (by interface or attribute, etc.)
           //Add type to type map   
        }
    }

处理AssemblyLoad事件可以处理动态加载的程序集。

据我了解,Type.GUID使用类型的程序集版本作为Guid生成算法的一部分。如果您增加程序集版本号,可能会导致麻烦。根据您的应用程序情况,使用另一个答案中描述的GetDeterministicGuid方法可能是明智的选择。


“我认为没有办法在首先构建Guid→Type映射之前完成这个操作”是正确的,因为在任何Assembly中每个Type只有在被引用时才会完全加载。CLR一直加载该程序集中的所有类型将效率低下,因为其中许多类型可能永远不会被使用或需要。因此,CLR无法查找还未被引用的GuidAttribute中的Guid值,也就无从得知。构建Guid到Type的映射至关重要,以确保这些类型也都被加载。 - Glenn Slayden

4
不要循环比较。填充一个 Dictionary<Type> 并使用 Contains 方法。
Dictionary<Type> types = new Dictionary<Types>();
... //populate 

if (types.Contains(someObject.GetType())) 
  //do something

这将确保您获得固定大小的条目,因为它们都将是对象引用(Type实际上是工厂对象的实例)。


是的,我正在考虑那个......在启动算法之前,我仍然需要预先填充字典,这会增加一些启动开销。我不确定具体增加了多少开销,所以我会进行一些测试,看看是否可行(但仍然比循环好)。 - Tamas Czinege
哈希查找比迭代比较要高效得多,因此如果比较发生在循环中,那么它非常值得。但是为了代码的可读性,我仍然会这样做。 - Peter Wone
你也可以将GUID映射到限定名称并存储在高效的数据结构(堆或trie)中。这个映射可以在编译时生成,并以快速解析的二进制格式存储。这样可以减少启动算法之前的开销。如果需要实际的“Type”对象,可以从限定名称惰性地创建它。 - Dirk Vollmar
一个哈希进入类命名空间?有趣。它必须是可能的,反射使用它。 - Peter Wone
Peter Wone:这是可能的,但是我仍然需要执行相同的循环/字典查找,因为哈希通常是单向的。我想,如果我将命名空间的哈希和未限定类型名称分开保留,那么速度会稍微快一些。 - Tamas Czinege
显示剩余2条评论

1
生成确定性GUID中的内容来看,有什么想法呢?
private Guid GetDeterministicGuid(string input)
{
    // use MD5 hash to get a 16-byte hash of the string:
    MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider();
    byte[] inputBytes = Encoding.Default.GetBytes(input);
    byte[] hashBytes = provider.ComputeHash(inputBytes);
    
    // generate a guid from the hash:
    Guid hashGuid = new Guid(hashBytes);
    return hashGuid;
}

并且加入 typeof().AssemblyQualifiedName。你可以将这些数据存储在一个 Dictionary<string, Guid> 集合中(或者,任何一个 <Guid, string>)。
这样,对于给定的类型,你将始终拥有相同的 GUID(警告:可能会发生冲突)。

我仍然需要进行某种查找,因为这个哈希机制本质上是单向的。我们又回到了同样的问题。 - Tamas Czinege

0
我会使用 typeof(class).GUID 在缓存字典中查找实例:
private Dictionary<Guid, class> cacheDictionary { get; set; }

我会编写一个方法,该方法返回字典和GUID作为参数,以便在字典中搜索类。

public T Getclass<T>()
    {
        var key = typeof(T).GUID;
        var foundClass= cacheDictionary.FirstOrDefault(x => x.Key == key);
        T item;
        if (foundClass.Equals(default(KeyValuePair<Guid, T>)))
        {
            item = new T()
            cacheDictionary.Add(key, item);
        }
        else
            item = result.Value;

        return item;
    }

并且我会为缓存使用单例模式, 调用代码可能如下所示:
   var cachedObject = Cache.Instance.Getclass<class>();

0
如果您掌控这些类,我建议:
public interface ICachable
{
    Guid ClassId { get; }
}

public class Person : ICachable
{
    public Guid ClassId
    {
        get {  return new Guid("DF9DD4A9-1396-4ddb-98D4-F8F143692C45"); }
    }
}

您可以使用Visual Studio中的工具->创建GUID来生成您的GUID。


谢谢,但不幸的是我不能控制类型。我必须能够处理任何类型(好吧,除了指针,但这与问题无关)。 - Tamas Czinege
也许你可以结合字典和 GUID 的想法。如果字典中不存在该类型,可以懒加载生成一个 GUID 并将其添加到字典中。使用字典键->类型名称,值->GUID。不过你需要想办法持久化字典,我想。 - Serkan

0

Mono文档报告说,一个模块有一个GUID元数据堆

也许Cecil可以帮助您根据其GUID查找类型?但不确定,它有一个GuidHeap类,似乎是生成guids,但也许这对您的缓存足够了?


在Mono上,如果类无法手动指定GUID,则Type.GUID始终返回GUID.Empty。(http://lists.ximian.com/pipermail/mono-devel-list/2005-September/014530.html) - EricLaw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接