如何动态收集封闭类型?

时间:2017-09-09 00:57:17

标签: c# generics reflection

假设我有一个如下定义的泛型类:

MyThing

稍后我想在不知道所有可能的类型参数的情况下收集任何封闭类型的MyThing<int>.Foo() 的所有调用次数,以便我可以打印如下列表:

  • MyThing<Bar>.Foo()的来电次数:142
  • public IEnumerable<Type> GetClosedMyThingTypes() { // what to put here? } :39
  • 的来电次数

所以我需要的是一种动态从运行时获取所有已创建的闭合类型的方法。类似的东西:

NumberOfCallsToFoo

然后我可以使用反射来获取{{1}}字段。这样做的目的是跟踪涉及许多类型和程序集的庞大系统。

更新: 我尝试重新制定我需要的东西。在执行程序的任意时刻,我想收集某个泛型类型的所有(迄今为止构建的)已关闭的类型(非实例)。

3 个答案:

答案 0 :(得分:1)

我相信您在运行时寻找的信息根本不可用,除非您自己提供。其他类似问题也强化了这种信念,例如List closed types that runtime has created from open generic types

不幸的是,自己动手将需要检测要跟踪的每种通用类型。如果你只有一个,那可能没问题。但是对于每种通用类型都必须这样做有点乏味。在这种情况下,我可能更喜欢查看一个AoP工具(例如PostSharp),它允许您以更自动化的方式添加此工具。

那就是说,鉴于你的问题陈述,我可能会这样做:

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

请注意,我使用基类来提供基础跟踪功能。我更喜欢这种方法,因为它封装了跟踪,以确保没有其他代码,无论是否意外,最终搞乱了跟踪信息。当然,这依赖于泛型类不继承任何其他类。你可以很容易地将它放在一个不同的,无关的类中......它就不会那么安全了。

演示程序:

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

收率:

C`1: 1
C`1: 2

请注意,generic属性的名称不包含type参数;它只是声明的泛型类型的名称。如果您想要一个更友好的名称,可以在这里查看:C# Get Generic Type Name

请注意ReportCounts()方法中的反射可能代价高昂。根据确切的用法,例如调用计数方法的频率与报告的频率,您可以通过将计数存储在Dictionary<Type, int>中或通过记忆传入或构建的委托来改进事物。 Expression访问每种类型的字段。

我的猜测是你不经常打电话给ReportCounts(),所以直接反思的方法很好。

当然,上面只处理一个计数。如果您正在处理多个方法,则可能需要多个字段,或者单个字段为Dictionary<string, int>,其中键是方法名称。您可以将其与每种类型的优化结合起来,最后在基类中使用Dictionary<Type, Dictionary<string, int>>

以下是一个变体,其中包含这些其他功能的一些示例:

  • 处理多种方法
  • 通过让泛型类型传递访问者委托
  • 来避免反射
  • 包含类型名称的简单“友好名称”扩展方法
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;
    }
}

演示程序:

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

收率:

C.M1: 1
C.M2: 2
C.M1: 3
C.M2: 4

答案 1 :(得分:1)

您可以在运行时执行此操作,但必须修改代码。

首先,声明一个静态的全局字符串列表,以包含已关闭类型的全名,以及添加到其中的方法:

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

然后为每个泛型类型添加一个静态构造函数。我们将利用静态构造函数为每个封闭类型运行一次的事实,即对SomeGenericType<int>进行一次调用,对SomeGenericType<double>进行第二次调用。例如:

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

最终节目:

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

运行它,输出为:

SomeGenericType<Double>
SomeGenericType<Int32>

答案 2 :(得分:0)

在运行时执行它是不可能的,.NET不会公开一组&#34;这种类型的所有实例我曾经创建过#34;。因此,您需要调整一些代码来收集您自己的内容。以下是一些选项:

糟糕的选择(技术上解决了您的问题):

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

更好的选择......不要存储类型,直接收集你想要的东西:

public class Tracker
{
    public static int NumberOfCallsToFoo;
}

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