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