1

Let's assume I have a generic class defined as follows:

public class MyThing<T> {
     private static int NumberOfCallsToFoo;
     public void Foo(T item) {
          NumberOfCallsToFoo++;
          ...
     }
}

Later I want to collect all the number of calls to any closed type of MyThing without knowing all possible type parameters so I can print a list such as:

  • Number of calls to MyThing<int>.Foo(): 142
  • Number of calls to MyThing<Bar>.Foo(): 39
  • etc.

So what I need is a way to dynamically get all created closed types from the run-time. Something like:

public IEnumerable<Type> GetClosedMyThingTypes() {
    // what to put here?
}

Then I could use reflection to get the NumberOfCallsToFoo field. The purpose of this is to trace a huge system with many types and assemblies involved.

UPDATE: I try to reformulate what I need. At an arbitrary moment in time of the execution of a program, I want to collect all the (so far constructed) closed types (not instances) of a certain generic type.

Dejan
  • 9,150
  • 8
  • 69
  • 117
  • You can use reflection `GetType().Name` to get the type name. – Orel Eraki Sep 09 '17 at 01:04
  • The challenge is not how to get the name, or how to get the `NumberOfCalls` field, but to get all closed types. – Dejan Sep 09 '17 at 01:07
  • I don't believe you can... Manual tracking list of types is likely the only option (and definitely trivial to implement/understand compared to any reflection you'd have to write) – Alexei Levenkov Sep 09 '17 at 01:10
  • _"what I need is a way to dynamically get all created closed types from the run-time"_ -- how is this not exactly the same question [you already asked earlier](https://stackoverflow.com/q/46121317)? And how does it not suffer from the same inherent issues that question did? – Peter Duniho Sep 09 '17 at 01:13
  • Manual tracking could be an option. However, the runtime also needs to track it. So I was wondering if there's a way to retrieve this info from the runtime. – Dejan Sep 09 '17 at 01:13
  • @PeterDuniho: haha, yes, I'm still trying to solve the same problem. And my hope was that this time I was clearer to formulate what I want to achieve. Can you please tell me what the inherent issues of this question? Happy to learn. – Dejan Sep 09 '17 at 01:15
  • _"the runtime also needs to track it"_ -- no, not really it doesn't. It certainly doesn't need to know all the possible call sites (which is what you seem to be asking). The run-time only needs to understand the type at the point at which the call is made; it has no need to track on-going where those calls are or what objects _might_ be called that way. – Peter Duniho Sep 09 '17 at 01:15
  • The runtime needs to know that `MyThing` was already constructed and that it has a static field with a value y. Otherwise, it couldn't be incremented. – Dejan Sep 09 '17 at 01:17
  • The biggest issue with your question is that it's not even entirely clear what you're trying to achieve (e.g. do you want only existing instantiated objects, for some given moment, or to anticipate all places in code such objects _could_ be created?). But more to the point are the comments (especially the first one on your other post), which call out the impossibility of expecting to be able to identify objects of closed generic types and the difficulty of analyzing the IL (which can be obtained via reflection) to determine where and how the generic type is used. – Peter Duniho Sep 09 '17 at 01:18
  • @PeterDuniho: does my UPDATE on this question help? – Dejan Sep 09 '17 at 01:22
  • Yes, thanks...that seems at a minimum like a more tractable problem. :) – Peter Duniho Sep 09 '17 at 01:24
  • I don't think what you want is possible. Nothing in .NET exposes this information. You'll need an alternate solution to whatever your problem is... –  Sep 09 '17 at 01:29

3 Answers3

1

I believe the information you seek at run-time is simply not available unless you provide it yourself. This belief is reinforced by other similar questions, such as List closed types that runtime has created from open generic types.

Unfortunately, doing it yourself will require instrumenting every generic type you want to track. If you have just one, it might be fine. But it could get a bit tedious having to do this for every generic type. In that case, I might prefer to look into an AoP tool (e.g. PostSharp) that would allow you to add this instrumentation in a more automated way.

That said, given your problem statement, I might approach it something like this:

class C
{
    protected static List<Type> _types = new List<Type>();

    public static void ReportCounts()
    {
        foreach (Type type in _types)
        {
            FieldInfo fi = type.GetField("_count", BindingFlags.Static | BindingFlags.NonPublic);

            Console.WriteLine($"{type.Name}: {fi.GetValue(null)}");
        }
    }
}

class C<T> : C
{
    static int _count;

    static C()
    {
        _types.Add(typeof(C<T>));
    }

    public void M()
    {
        _count++;
    }
}

Note that I use a base class to provide the underlying tracking functionality. I prefer this approach, because it encapsulates the tracking to ensure no other code, accidentally or otherwise, winds up messing with the tracking information. Of course, this relies on the generic class not inheriting any other. You could easily put this in a different, unrelated class…it just wouldn't be quite as safe.

Demo program:

class Program
{
    static void Main(string[] args)
    {
        C<int> c1 = new C<int>();
        C<bool> c2 = new C<bool>();

        c1.M();
        c2.M();
        c2.M();

        C.ReportCounts();
    }
}

Yields:

C`1: 1
C`1: 2

Note that the name of the generic property doesn't include the type parameter; it's just the name of the declared generic type. If you want a more friendly name, you can look here: C# Get Generic Type Name

Note that the reflection in the ReportCounts() method is potentially costly. Depending on the exact usage, such as how often the counted method is called vs. how often the report is made, you could improve on things by storing the counts in a Dictionary<Type, int> or by memoizing a delegate passed in or built with Expression to access the field for each type.

My guess is that you don't call ReportCounts() very often and so the direct reflection approach is fine.

The above handles only one count, of course. If you're dealing with multiple methods, you might want multiple fields, or a single field that is a Dictionary<string, int> where the key is the method name. You can combine that with the per-type optimization and wind up with Dictionary<Type, Dictionary<string, int>> in the base class instead.

Here's a variation that includes some examples of these other features:

  • Handles multiple methods
  • Avoids reflection by having the generic type pass an accessor delegate
  • Includes a simple "friendly name" extension method for type names
class C
{
    protected static List<Func<(Type, Dictionary<string, int>)>>
        _countFieldAccessors = new List<Func<(Type, Dictionary<string, int>)>>();

    protected static void _AddType(Func<(Type, Dictionary<string, int>)> fieldAccess)
    {
        _countFieldAccessors.Add(fieldAccess);
    }

    public static void ReportCounts()
    {
        foreach (Func<(Type, Dictionary<string, int>)> fieldAccess in _countFieldAccessors)
        {
            var (type, counts) = fieldAccess();

            foreach (var kvp in counts)
            {
                Console.WriteLine($"{type.GetFriendlyName()}.{kvp.Key}: {kvp.Value}");
            }
        }
    }
}

class C<T> : C
{
    static Dictionary<string, int> _counts = new Dictionary<string, int>();

    static void _Increment(string name)
    {
        int count;

        _counts.TryGetValue(name, out count);
        _counts[name] = count + 1;
    }

    static C()
    {
        _AddType(() => (typeof(C<T>), _counts));
    }

    public void M1()
    {
        _Increment(nameof(M1));
    }

    public void M2()
    {
        _Increment(nameof(M2));
    }
}

static class Extensions
{
    public static string GetFriendlyName(this Type type)
    {
        return type.IsGenericType ?
            $"{type.Name.Substring(0, type.Name.IndexOf('`'))}<{string.Join(",", type.GetGenericArguments().Select(t => t.Name))}>" :
            type.Name;
    }
}

Demo program:

class Program
{
    static void Main(string[] args)
    {
        C<int> c1 = new C<int>();
        C<bool> c2 = new C<bool>();

        c1.M1();
        c1.M2();
        c1.M2();
        c2.M1();
        c2.M1();
        c2.M1();
        c2.M2();
        c2.M2();
        c2.M2();
        c2.M2();

        C.ReportCounts();
    }
}

Yields:

C.M1: 1
C.M2: 2
C.M1: 3
C.M2: 4
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • Thanks for all the details. This is the deluxe manual tracking solution with registration in the static c'tor - I thought of that. However, my hope was to somehow access this information from the system - because somewhere it is around. – Dejan Sep 09 '17 at 01:47
1

You can do this at run time, but you have to modify your code.

First, declare a static, global list of strings to contain the full name of the closed types, along with a method for adding to it:

class Program
{
    static public List<string> _closedTypes = new List<string>();

    static public void RegisterClosedType(Type t)
    {
        _closedTypes.Add(String.Format("{0}<{1}>",
            t.Name.Split('`')[0],
            String.Join(",", t.GenericTypeArguments.Select(q => q.Name))
            ));
    }
}

Then add a static constructor to each of your generic types. We are going to take advantage of the fact that a static constructor runs exactly once for each closed type, i.e. there will be one call for SomeGenericType<int> and a second call for SomeGenericType<double>. Example:

class SomeGenericType<T>
{
    static SomeGenericType()
    {
        Program.RegisterClosedType(typeof(SomeGenericType<T>));    
    }
}

Final program:

class Program
{
    static public List<string> _closedTypes = new List<string>();

    static public void RegisterClosedType(Type t)
    {
        _closedTypes.Add(String.Format("{0}<{1}>",
            t.Name.Split('`')[0],
            String.Join(",", t.GenericTypeArguments.Select(q => q.Name))
            ));
    }

    static public void Main()
    {
        var d = new SomeGenericType<double>();
        var i = new SomeGenericType<int>();

        DumpClosedTypes();
    }

    static void DumpClosedTypes()
    {
        foreach (var s in _closedTypes)
        {
            Console.WriteLine(s);
        }
    }
}

Run it and the output is:

SomeGenericType<Double>
SomeGenericType<Int32>
John Wu
  • 50,556
  • 8
  • 44
  • 80
  • Thanks. Basically, the same approach like Peter Duniho's. – Dejan Sep 09 '17 at 02:06
  • Yeah, I was writing this answer for [your other question](https://stackoverflow.com/questions/46121317/how-can-i-get-all-closed-types-of-a-certain-generic-type/46126504#46126504) and didn't see his answer. My answer also shows how to get the generic type arguments, which seems important. – John Wu Sep 09 '17 at 02:09
  • Yes. Sorry for the mess :( – Dejan Sep 09 '17 at 02:10
0

Doing it at runtime isn't possible, .NET doesn't expose a set of "all instances of this type I've created ever". So you'll need to tweak some code to collect what you what yourself. Here are some options:

Bad option (that technically solves your question):

public class Tracker
{
    public static List<Type> Types = new List<Type>();
}

public class MyThing<T>
{
    static MyThing()
    {
        // This is the static constructor, and is called once for every type
        Tracker.Types.Add(typeof(MyThing<T>));
    }
}

Better option... don't store the types, just directly collect what you want:

public class Tracker
{
    public static int NumberOfCallsToFoo;
}

public class MyThing<T>
{
    public void Foo(T item) {
        Tracker.NumberOfCallsToFoo++;
        ...
    }
}
  • Sure, I can track manually. BTW, the second option is not an option because I want to have the number of calls per generic parameter type (see my question). – Dejan Sep 09 '17 at 01:43
  • You could use something like `Tracker.IncrementCalls(typeof(T))` to send the generic type to the `Tracker`. – Bradley Uffner Sep 09 '17 at 03:21