如何检查静态构造函数是否已被调用?

7

我有一些从数据库缓存数据的类,这些类在静态构造函数被调用时加载数据。

我需要调用所有这些类的静态Reload方法,除了那些尚未初始化的类。

例如:City从数据库缓存数据。

public class City
{
    public City Get(string key)
    {
        City city;
        FCities.TryGetValue(key, out city);
        return city;
    }
    private static Dictionary<string, City> FCities;

    static City()
    {
        LoadAllCitiesFromDatabase();
    }

    public static void Reload()
    {
        LoadAllCitiesFromDatabase();
    }

    private static void LoadAllCitiesFromDatabase()
    {
        // Reading all citynames from database (a very slow operation)
        Dictionary<string, City> loadedCities = new Dictionary<string, City>();
        ...
        FCities = loadedCities;
    }
}

问题在于,可能尚未使用City(它可能不在此服务中使用),因此没有理由从数据库加载它。
我的重新加载所有方法看起来很像这样:
public static class ReloadAll
{
    public static void Do()
    {
        foreach (Type classType in AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Where(t => t.IsClass && !t.IsAbstract))
        {
            MethodInfo staticReload = classType.GetMethods().FirstOrDefault(m => m.IsStatic && m.IsPublic && m.ReturnType == typeof(void) && m.Name == "Reload" && m.GetParameters().Length == 0);
            if (staticReload != null)
            {
                if (StaticConstructorHasBeenCalled(classType))
                    staticReload.Invoke(null, null);
            }
        }
    }

    private bool StaticConstructorHasBeenCalled(Type classType)
    {
        // How do I check if static constructor has been called?
        return true;   
    }
}

我需要一些关于StaticConstructorHasBeenCalled的实现方面的帮助。


我在考虑在每个类上放置一个静态字段来跟踪静态构造函数是否已被调用;但仅使用反射调用该字段很可能会触发静态构造函数... - luksan
第一次初始化推迟到实际需要的时候是否可以接受?如果可以,您可以将数据访问包装在 Lazy(T) 中,并使用 IsValueConstructedReload 方法内部进行检查。 - Damien_The_Unbeliever
我希望有一种使用反射来判断静态构造函数是否被调用的方法,这样我就不必改变太多东西才能使它工作。另一种选择是拥有一个存储所有缓存类的仓库单例 - 非常感谢您所有美好的答案。 - Casperah
5个回答

8
乍一看,我以为这可能是一个问题,其中<grin>量子力学哥本哈根解释可能适用(“一旦你看它,它就会改变”)。</grin>也就是说,你在类内执行观察是否初始化的任何操作都可能导致它自行初始化...但是你不必在类中执行此操作,只需在其他地方保留列表(而非任何这些静态类中的列表),当每个静态类初始化时将其加入列表。然后在重置函数中,只需遍历列表中的类。

我也有同样的想法。列表本身需要成为一个静态类。它可以拥有静态方法,例如 SetClassInitializedGetClassInitialized,这些方法将查看内部列表。 - luksan
+1 对于问题的良好描述和可能的解决方案。事实上,我来自哥本哈根,这使得它更有趣。 - Casperah

2

在静态构造函数中不应使用长时间运行的操作,或者至少不应同步运行。静态构造函数是隐式运行的,当隐式执行需要显著时间时,会产生有趣的错误 :)

此外,如果您确实使用静态构造函数、方法或其他维护状态的内容,请尝试将它们隔离在一个完全静态的类中,这样它们就会像单例一样,在大多数情况下起作用。我会将实现更改为以下内容:

public static class CityRepository
{
   private static bool areCitiesLoaded;
   private List<City> cities;

   static CityRepository()
   {
     areCitiesLoaded = false;
     cities = new List<City>();
   }

   //method that will be called in all other method, to ensure
   //that the cities are loaded
   private static void EnsureLoad()
   {
      if (!areCitiesLoaded)
      {
        LoadAllCitiesFromDatabase();
        areCitiesLoaded = true;
      }
   }
}

public class City {} //city instance methods

+1 你说得对,静态构造函数不应该用于长时间运行的操作,我在制定我的解决方案时会考虑到这一点。 - Casperah
这对我很有用。我发现静态构造函数在调用类的静态方法的第一次实例化之前被调用。因此,当您首次调用静态方法时,构造函数会自动调用。https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors 阅读第一行。 - LexFerrinson

2
如果您有多个这些类,为什么不通过工厂或管理类来控制它们的实例化。这样可以跟踪已使用的类并在适当的时候调用重新加载方法。

+1 这是正确的方法,但我已经收到了4个答案,其中多少与此答案相同。 - Casperah

1
你可以使用单例模式,并添加一个字段来告诉你唯一实例是否已经创建。
实际上不需要制作单例,只需保留静态类,并在调用应该返回数据的属性getter时加载数据:
static class City
{
   static bool _loaded = false;

   public bool Loaded { get { return _loaded; } }

   public static List<C> Data
   {
      get
      {
         if (!_loaded)
         {
             doLoading();
             _loaded = true
         }
      }
   }
}

2
当您访问该字段以确定类型是否已初始化时,会发生什么? - Kirk Woll
我没有考虑访问单例字段,而是另一个可以告诉单例是否已初始化的字段。 - ppetrov
一个不错的答案,只是这些类并不像单例一样运作。它们通常是该类的实例容器。 - Casperah
@ppetrov,但你没有抓住重点——访问类上的任何字段都会触发静态初始化程序。 - Kirk Woll
这并不告诉你静态构造函数是否已经被调用。(在示例代码中甚至没有静态构造函数...) - Laoujin
显示剩余3条评论

1
我想知道是否有办法查看静态构造函数是否被调用。我认为答案是否定的,但一个解决方法是创建一个管理器来跟踪存储库。 目标是尽可能少地更改现有类。
我的解决方案是创建一个管理器类:
public static class RepositoryManager
{
    public delegate void Reload();
    private static List<Reload> FRepositories = new List<Reload>();

    public static void Register(Reload repository)
    {
        lock (FRepositories)
        {
            FRepositories.Add(repository);
        }

        repository();
    }

    public static void ReloadAll()
    {
        List<Reload> list;
        lock (FRepositories)
        {
            list = new List<Reload>(FRepositories);
        }

        foreach (Reload repository in list)
            repository();
    }
}

使用City类的示例,更改将仅限于静态构造函数。
public class City
{
    // ...

    static City()
    {
        RepositoryManager.Register(LoadAllCitiesFromDatabase);
    }

    // ...
}

我的 ReloadAll 方法将会非常简单:

public void ReloadAll()
{
    RepositoryManager.ReloadAll();
}

感谢您所有的回答,我已经奖励了每个建议使用管理器作为解决方案的人。
这种解决方案的缺点是,每当有人创建一个需要定期重新加载/更新/清除的存储库时,他们必须记得使用RepositoryManager。

+1 非常优雅的解决方案!变化不大,简单明了,完美地达到了目标。 - ppetrov

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