13

For various reasons, I need to implement a type caching mechanism in C#. Fortunately, the CLR provides Type.GUID to uniquely identify a type. Unfortunately, I can't find any way to look up a type based on this GUID. There's Type.GetTypeFromCLSID() but based on my understanding of the documentation (and experiments) that does something very, very different.

Is there any way to get a type based on its GUID short of looping through all the loaded types and comparing to their GUIDs?

EDIT: I forgot to mention that I would really like a "type fingerprint" of fixed width, that's why the GUID is so appealing to me. In a general case, of course, the fully qualified name of the type would work.

Tamas Czinege
  • 118,853
  • 40
  • 150
  • 176
  • `typeof().AssemblyQualifiedName` doesn't fit for you? – Rubens Farias Jan 12 '10 at 00:38
  • Rubens: It would, but I'd much preferred something of a fixed width as it would simplify my algorithm a lot (which is outside the scope of this question) and the fixed size of 16 bytes of a GUID would be ideal. Maybe I could use the hash code of the qualified name but then again I would have the same problem with looking up the type based on the hash. – Tamas Czinege Jan 12 '10 at 00:40
  • 1
    and this can also be useful: http://ayende.com/Blog/archive/2008/01/12/System.Type.GUID-stability.aspx – Rubens Farias Jan 12 '10 at 00:43
  • Keep in mind that hashing is a one-way process. – Anon. Jan 12 '10 at 00:45
  • @DrJokepu, can you please elaborate in how to use that type identifier? By using `AssemblyQualifiedName`, you could do `Type.GetType(typename)` – Rubens Farias Jan 12 '10 at 00:55
  • Rubens: The thing is, I need to persist this stuff on the disk. If I have fixed-width data structures, looking up elements is a lot simpler as I don't have to keep track of a list of offsets. – Tamas Czinege Jan 12 '10 at 00:58
  • Nice! But if you can guarantee type name uniqueness, you can to use `Type.FullName` (without assembly information) as its smaller – Rubens Farias Jan 12 '10 at 01:00
  • Unfortunately, FullName is a variable-length string as well. – Tamas Czinege Jan 12 '10 at 01:01
  • How about a trie mapping the qualified name to the type then? Wouldn't make that the requirement for a fixed-length key redundant? – Dirk Vollmar Jan 12 '10 at 01:13
  • dive: Yes I've thought about that. Thing is though, if I generated my arbitrary mapping, I would have to persist the mapping as well to the disk and I was hoping that I can avoid that (I admit, out of pure lazyness) – Tamas Czinege Jan 12 '10 at 01:14
  • I decided to take with the "build-the-dictionary-on-startup" approach. Thanks all you folks for your help! – Tamas Czinege Jan 12 '10 at 02:15
  • Building it at startup incidentally guarantees that it contains current information. – Peter Wone Jan 22 '10 at 00:30
  • @Peter Wone: Building on startup has its obvious drawbacks as well. For example, if the process loads another assembly dynamically after after start-up, the types defined in the newly loaded assembly don't get catalogued. – Tamas Czinege Jan 22 '10 at 09:20
  • 2
    To cache or not to cache: that is the question; whether tis nobler in the mind to suffer the slings and arrows of outrageous fetch times, or build a cache, and in composing lose some. – Peter Wone Jan 22 '10 at 09:39

7 Answers7

6

why not use the designated property for that, ie. AssemblyQualifiedName? This property is documented as "can be persisted and later used to load the Type".

The GUID is for COM interop.

Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
  • 1
    Thanks, but as I mentioned in one of the comments, ideally I would have type "fingerprint" of a fixed width, dealing with variable length strings would make my life a bit more complicated. But yeah, generally if you don't have such a requirement then I agree, the full qualified name is the way to go. – Tamas Czinege Jan 12 '10 at 00:47
  • 2
    @Doc: What if you create more than 2^128 types? – Anon. Jan 12 '10 at 00:48
  • 5
    Anon: Ain't going to happen :) The universe will collapse well before all the computers of the world together can create 2^128 types. – Tamas Czinege Jan 12 '10 at 00:50
  • I'm a bit surprised to hear that variable length poses problems. what data type strcuture you have in mind for the cache that would have problems with a variable length lookup key, in such a high end world as CLR? – Remus Rusanu Jan 12 '10 at 01:01
  • Remus Rusanu: Fixed length would be ideal because I need to persist this stuff on the disk and access it quickly. Fixed length data structures mean no need to keep track of a list of offsets as I can find a record easily by knowing the index. – Tamas Czinege Jan 12 '10 at 01:03
  • 1
    Variable length fields are blazing fast for an in process relational engine like SQLite. – Remus Rusanu Jan 12 '10 at 01:16
  • Well, SQLite is using binary trees for indexing and has to keep track of the offsets of strings to have this working. I'm not saying it's not fast (especially if the OS is caching the disk) but it's at least twice as much effort. – Tamas Czinege Jan 12 '10 at 01:19
  • Are you going to use a buffer pool, read aheads, fancy caching policies like a relational engine does? Is the advantage of easily expressable complex interogation criteria (ie. SQL lanhuage) something that doesn't saves you any development time down the road? I'm not saying use SQLite, but don't dismiss it so easily. A product like that has years of know-how, IO calibration, optimizations and tricks around TLB cache hits and L2 coherency you probably aren't thinking at. – Remus Rusanu Jan 12 '10 at 01:37
  • 2
    Remus Rusanu: I really don't want to go into the details as they are really out of the scope but I have very good reasons to use the data structures I do and I generally know what I'm doing (at least I'd like to think so). Development time is a secondary concern here. I'd love to talk about it as it is really interesting stuff but I don't think this question is the appropriate medium to do so unfortunately. Thanks for the advice though, generally, you're right, off-the-shelf stuff is most useful most of the time. – Tamas Czinege Jan 12 '10 at 01:44
5

This may just be a summary of answers already posted, but I don't think there is a way to do this without first building a map of Guid->Type.

We do this in our framework on initialization:

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

Handling the AssemblyLoad event takes care of dynamically loaded assemblies.

From what I understand, Type.GUID uses the assembly version of the type as part of the Guid generation algorithm. This may lead to trouble if you increment your assembly version numbers. Using the GetDeterministicGuid method described in another answer would probably be advisable, depending on your application.

Joe Enzminger
  • 11,110
  • 3
  • 50
  • 75
  • *"I don't think there is a way to do this without first building a map of Guid->Type"* **Correct**, because each `Type` in any `Assembly` is only fully loaded on demand when actually referenced. It would be inefficient for the CLR to always load every `Type` in the assembly whenever that assembly is first loaded, because many of its types may never be needed or used. Therefore, the CLR has no knowledge of—or way to lookup—the `Guid` value within a `GuidAttribute` for a `Type` that hasn't been referenced yet. Building a Guid-to-Type map crucially ensures that those types are also all loaded. – Glenn Slayden Aug 18 '20 at 04:26
4

Don't loop to compare. Populate a Dictionary<Type> and use the Contains method.

Dictionary<Type> types = new Dictionary<Types>();
... //populate 

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

This will certainly give you a fixed size entry, since all of them will be object references (instances of Type essentially being factory objects).

Peter Wone
  • 17,965
  • 12
  • 82
  • 134
  • Yeah I was considering that... I would still have to pre-populate the dictionary before launching the algorithm and that would add a bit of an overhead to the start-up. I'm no sure how much exactly so I'll do some tests and see if it's feasible to do (it's still better than looping though). – Tamas Czinege Jan 12 '10 at 00:52
  • The hash lookup will be ridiculously more efficient than iterative comparison, so if the comparison happens in a loop it's well worthwhile. But I'd do it anyway for code legibility. – Peter Wone Jan 12 '10 at 00:56
  • You could also store a mapping of GUID to qualified name in an efficient data structure (heap or trie). This mapping could already be generated at compile time and be stored in a quickly-to-parse binary format. That could reduce the overhead before launching your algorithm. If you need an actual `Type` object you could create it lazily from the qualified name. – Dirk Vollmar Jan 12 '10 at 00:58
  • divo: Unfortunately the compile time generation is out of question as I will have to deal with types not known at compile time. – Tamas Czinege Jan 12 '10 at 01:00
  • A hash into the class namespace? Interesting. It must be possible, reflection uses it. – Peter Wone Jan 12 '10 at 01:01
  • Peter Wone: It would be possible, but then again, I would have to do the same loop/dictionary lookup anyway as hashes are normally unidirectional. I suppose if I kept the hash of the namespace and the unqualified type name separately, that would speed up things a bit. – Tamas Czinege Jan 12 '10 at 01:05
  • I've ran some quick tests and it turned out that building a dictionary of all loaded types is surprisingly quick so I'll go with that approach. Thanks a lot. – Tamas Czinege Jan 12 '10 at 02:14
1

What about (from Generating Deterministic GUIDs):

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

And throw in that typeof().AssemblyQualifiedName. You could to store this data inside a Dictionary<string, Guid> collection (or, whatever, a <Guid, string>).

This way you'll have always a same GUID for a given type (warning: collision is possible).

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Rubens Farias
  • 57,174
  • 8
  • 131
  • 162
  • I would still need to do a lookup somehow as this hash mechanism is inherently unidirectional. We're back to the same problem. – Tamas Czinege Jan 12 '10 at 01:11
0

If you are in control of these classes I would recommend:

public interface ICachable
{
    Guid ClassId { get; }
}

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

You can generate your GUIDs using Visual Studio, Tools->Create Guid.

Serkan
  • 160
  • 1
  • 1
  • 7
  • Thanks, but unfortunately I'm not in control of the types. I must need to be able to deal with any type (well, except for pointers but that's irrelevant to the question). – Tamas Czinege Jan 12 '10 at 00:55
  • Maybe you can combine Dictionary idea and Guids. You can populate it lazily, if type doesn't exist in the dictionary generate a Guid and add it to the dictionary. Use a dictionary key->Type Name, Value->Guid. Some how you need to persist the dictionary, as well I suppose – Serkan Jan 12 '10 at 01:02
0

The Mono documentation reports that a module has a Metadata heap of guids.

Perhaps Cecil might help you lookup a type based on its guid? Not sure though, there is a GuidHeap class, it seems to be generating the guids though, but perhaps this is enough for your cache to work?

si618
  • 16,580
  • 12
  • 67
  • 84
  • On Mono, Type.GUID always returns GUID.Empty if the class fails to manually specify a GUID. (http://lists.ximian.com/pipermail/mono-devel-list/2005-September/014530.html) – EricLaw Oct 24 '12 at 16:27
0

I would use the typeof (class).GUID to find the instance in the cache dictionary:

private Dictionary<Guid, class> cacheDictionary { get; set; }

and I would have a method to return the dictionary and the GUID as parameter of the method to search for the class in the dictionary.

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

and I would use a singleton pattern for the cache,

and the call would be something like the code below:

   var cachedObject = Cache.Instance.Getclass<class>();