静态构造函数有啥用?
Posted
技术标签:
【中文标题】静态构造函数有啥用?【英文标题】:What is the use of static constructors?静态构造函数有什么用? 【发布时间】:2011-05-29 06:20:57 【问题描述】:请给我解释一下静态构造函数的使用。为什么以及何时创建静态构造函数,是否可以重载?
【问题讨论】:
应该注意的是,有一些静态构造方法(例如,查找Singleton Design Pattern)用于隐藏用于实例化类的实际构造函数。这使作者可以更好地控制他们的类的使用方式。 【参考方案1】:来自Static Constructors (C# Programming Guide):
静态构造函数用于 初始化任何静态数据,或 执行需要的特定操作 只执行一次。它被称为 在第一个之前自动 创建实例或任何静态的 成员被引用。
静态构造函数有以下 属性:
静态构造函数不接受访问修饰符或有参数。
在创建第一个实例或引用任何静态成员之前,会自动调用静态构造函数来初始化类。
不能直接调用静态构造函数。
用户无法控制程序中何时执行静态构造函数。
静态构造函数的典型用途是当类使用日志文件并且构造函数用于将条目写入该文件时。
在为非托管代码创建包装类时,静态构造函数也很有用,此时构造函数可以调用
LoadLibrary
方法。
【讨论】:
【参考方案2】:不,你不能超载它;静态构造函数可用于初始化与类型关联的任何静态字段(或任何其他按类型操作) - 特别适用于将所需的配置数据读入只读字段等。
它在第一次需要时由运行时自动运行(确切的规则很复杂(参见“beforefieldinit”),并且在 CLR2 和 CLR4 之间进行了微妙的更改)。除非你滥用反射,否则它保证最多运行一次(即使两个线程同时到达)。
【讨论】:
感谢您的回复。您能否向我提供有关您的句子“除非您滥用反射,否则它保证最多运行一次”的更多详细信息.. 关于静态构造函数的反射可以做什么.. @Rajesh - 找到方法并调用Invoke
20 次。
嗨@MarcGravell,请澄清CLR2和CLR4静态构造函数行为之间的区别。还有,这是否意味着静态构造函数是线程安全的?
@Johnny_D 很确定有条件可以在 CLR4 中推迟它们 - 确切的场景需要一些挖掘。重新线程安全:在理智的情况下,是的 - 基本上。如果您通过反思滥用它们:没那么多
对于任何对静态构造函数初始化如何从 CLR 2 更改为 CLR 4 感兴趣的人,Jon Skeet 的博客文章 (codeblog.jonskeet.uk/2010/01/26/…) 总结得很好:CLR 2 使用急切初始化,而 CLR 4 使用延迟初始化。 【参考方案3】:
您可以使用静态构造函数来初始化静态字段。在使用这些字段之前,它会在不确定的时间运行。 Microsoft 的文档和许多开发人员警告说,类型上的静态构造函数会带来大量开销。 最好避免使用静态构造函数以获得最大性能。 更新:您不能在同一个类中使用多个静态构造函数,但是您可以将其他实例构造函数与(最多)一个静态构造函数一起使用。
【讨论】:
这可能非常明显,但我认为这个答案触及了一个关键点,仅隐含在例如 marc 的答案中——静态构造函数不能代替实例构造函数。它们在创建第一个实例之前被调用一次,以设置静态属性等。实例构造函数继续像它们一直操作的那样工作,设置特定于该实例的东西。静态构造器:
static 类关注点::
instance 构造器:
instance 级别关注点有一定道理。 ;^)【参考方案4】:
1.它只能访问类的静态成员。
原因:非静态成员特定于对象实例。如果允许静态构造函数作用于非静态成员,它将反映所有对象实例的变化,这是不切实际的。
2.静态构造函数中不能有参数。
原因:由于它将被CLR调用,所以没有人可以将参数传递给它。 3.只允许一个静态构造函数。
原因:重载需要两种方法在方法/构造函数定义方面有所不同,这在静态构造函数中是不可能的。
4.不应该有访问修饰符。
原因:同样的原因是对静态构造函数的调用是由 CLR 而不是由对象进行的,不需要对其进行访问修饰符
【讨论】:
【参考方案5】:当您有相互依赖的静态字段以使初始化顺序很重要时,静态构造函数也非常有用。如果您通过更改字段顺序的格式化程序/美化程序运行您的代码,那么您可能会发现自己在未预料到的地方出现空值。
例子:假设我们有这个类:
class ScopeMonitor
static string urlFragment = "foo/bar";
static string firstPart= "http://www.example.com/";
static string fullUrl= firstPart + urlFragment;
当您访问fullUr
时,它将是“http://www.example.com/foo/bar”。
几个月后,您将清理代码并按字母顺序排列字段(假设它们是更大列表的一部分,因此您不会注意到问题)。你有:
class ScopeMonitor
static string firstPart= "http://www.example.com/";
static string fullUrl= firstPart + urlFragment;
static string urlFragment = "foo/bar";
您的 fullUrl
值现在只是“http://www.example.com/”,因为在设置 fullUrl
时尚未初始化 urlFragment
。不好。因此,您添加一个静态构造函数来处理初始化:
class ScopeMonitor
static string firstPart= "http://www.example.com/";
static string fullUrl;
static string urlFragment = "foo/bar";
static ScopeMonitor()
fullUrl= firstPart + urlFragment;
现在,无论字段的顺序如何,初始化始终是正确的。
【讨论】:
如果您使用的是 ReSharper,那么我相信在初始化 urlFragment 之前尝试设置 fullUrl 时,它会在这种情况下警告您。尽管如此,还是很好的例子。 我们知道静态构造函数每次都会在字段初始化之后执行吗?我猜不是因为安德鲁上面的回答说“在创建第一个实例或引用任何静态成员之前,会自动调用静态构造函数来初始化类。”。 @NoChance 您是否询问静态构造函数是否可以确信直接初始化的字段(在上面的示例中为 firstPart 和 urlFragment)将在调用该静态构造函数之前被初始化?答案是肯定的,只要字段列在静态构造函数之前。来自 MS 文档:如果静态字段变量初始值设定项存在于静态构造函数的类中,它们将按照它们在静态构造函数执行之前出现在类声明中的文本顺序执行。 @NoChance 重新阅读我的评论,当我说带有初始化程序的字段需要在静态构造函数之前时,我相信我说错了。无论它们在文件中的什么位置,它们都将在执行静态构造函数之前被初始化。【参考方案6】:我们为什么以及何时创建静态构造函数...?
使用静态构造函数的一个特定原因是创建一个“超级枚举”类。这是一个(简单的、人为的)示例:
public class Animals
private readonly string _description;
private readonly string _speciesBinomialName;
public string Description get return _description;
public string SpeciesBinomialName get return _speciesBinomialName;
private Animals(string description, string speciesBinomialName)
_description = description;
_speciesBinomialName = speciesBinomialName;
private static readonly Animals _dog;
private static readonly Animals _cat;
private static readonly Animals _boaConstrictor;
public static Animals Dog get return _dog;
public static Animals Cat get return _cat;
public static Animals BoaConstrictor get return _boaConstrictor;
static Animals()
_dog = new Animals("Man's best friend", "Canis familiaris");
_cat = new Animals("Small, typically furry, killer", "Felis catus");
_boaConstrictor = new Animals("Large, heavy-bodied snake", "Boa constrictor");
你会使用它与任何其他枚举非常相似(在语法外观上):
Animals.Dog
与常规enum
相比,它的优势在于您可以轻松封装相关信息。一个缺点是您不能在switch
语句中使用这些值(因为它需要常量值)。
【讨论】:
【参考方案7】:Static constructor
只调用创建的类的第一个实例。并用于执行在类的生命周期中只需要执行一次的特定操作。
【讨论】:
【参考方案8】:来自微软文档 https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors
静态构造函数(C# 编程指南)
静态构造函数用于初始化任何静态数据,或执行只需要执行一次的特定操作。在创建第一个实例或引用任何静态成员之前自动调用它。
class SimpleClass
// Static variable that must be initialized at run time.
static readonly long baseline;
// Static constructor is called at most one time, before any
// instance constructor is invoked or member is accessed.
static SimpleClass()
baseline = DateTime.Now.Ticks;
备注
静态构造函数具有以下属性:
静态构造函数不接受访问修饰符或参数。 一个类或结构只能有一个静态构造函数。 不能继承或重载静态构造函数。 不能直接调用静态构造函数,只能由公共语言运行时 (CLR) 调用。它是自动调用的。 用户无法控制何时在程序中执行静态构造函数。 自动调用静态构造函数。它在创建第一个实例或引用该类(不是其基类)中声明的任何静态成员之前初始化该类。静态构造函数在实例构造函数之前运行。类型的静态构造函数在调用分配给事件或委托的静态方法时调用,而不是在分配时调用。如果静态字段变量初始值设定项存在于静态构造函数的类中,它们将按照它们在类声明中出现的文本顺序执行。初始化程序在静态构造函数执行之前立即运行。 如果您不提供静态构造函数来初始化静态字段,则所有静态字段都将初始化为其默认值,如 C# 类型的默认值中所列。 如果静态构造函数抛出异常,运行时不会再次调用它,并且该类型将在应用程序域的生命周期内保持未初始化状态。最常见的是,当静态构造函数无法实例化类型或静态构造函数中发生未处理的异常时,会引发 xref:System.TypeInitializationException 异常。对于未在源代码中明确定义的静态构造函数,故障排除可能需要检查中间语言 (IL) 代码。 静态构造函数的存在阻止了添加外部参照:System.Reflection.TypeAttributes.BeforeFieldInit 类型属性。这会限制运行时优化。 声明为static readonly
的字段只能作为其声明的一部分或在静态构造函数中分配。如果不需要显式静态构造函数,请在声明时初始化静态字段,而不是通过静态构造函数来进行更好的运行时优化。
运行时在单个应用程序域中调用静态构造函数不超过一次。该调用是在基于类的特定类型的锁定区域中进行的。静态构造函数的主体中不需要额外的锁定机制。为了避免死锁的风险,不要在静态构造函数和初始化程序中阻塞当前线程。例如,不要等待任务、线程、等待句柄或事件,不要获取锁,也不要执行阻塞并行操作,例如并行循环、Parallel.Invoke
和并行 LINQ 查询。
[!注意] 虽然不能直接访问,但应记录显式静态构造函数的存在,以帮助解决初始化异常。
用法
静态构造函数的典型用途是当类使用日志文件并且构造函数用于将条目写入该文件时。 静态构造函数在为非托管代码创建包装类时也很有用,此时构造函数可以调用LoadLibrary
方法。
静态构造函数也是对无法在编译时通过类型参数约束检查的类型参数强制执行运行时检查的便利位置。
【讨论】:
以上是关于静态构造函数有啥用?的主要内容,如果未能解决你的问题,请参考以下文章