.net NET MVC项目中静态类和单例有什么区别?

xxe27gdn  于 2023-03-20  发布在  .NET
关注(0)|答案(7)|浏览(165)

我理解静态类和单例之间的区别,重要的是单例可以被示例化一次,而静态类不需要示例。
这个问题是从一个.NET MVC项目的Angular 提出的,以帮助我在使用它们中的任何一个之间做出决定。
因此,假设我有一个类,其方法类似于下面给出的示例:
1.我有一个类似ConvertMeterToMiles(int mtr)的方法,其中没有注入依赖项。
1.或者像SendEmail(str eaddress)这样的方法,其中没有注入依赖项,但它示例化new SMTPClient...,然后在finally中处理SMTPClient
假设我想把这个方法放到实用服务类中,那么我应该创建一个静态类还是单例(带依赖注入的ofcource)?
我理解没有限定作用域或 transient 的意义,因为拥有新示例没有任何好处。

oprakyz7

oprakyz71#

我想说,在应用程序的上下文中,差异可以归结为是否使用DI以及谁在控制生存期/示例化/注入。
如果某些功能不会因环境或其他可变性来源而有所不同,那么将其移到某个静态helper类中是非常好的,例如ConvertMeterToMiles似乎是这种处理的一个很好的候选者。
另一方面,SendEmail似乎不是一个-可能存在您不想发送电子邮件的环境(例如测试),或者将来您预计会有多个实现(或需要重新实现)以实现此功能(例如,对于某些上下文,可以使用例如队列来延迟电子邮件发送,队列将由某些后台工作者或另一服务来处理)。在这种情况下,您可以充分利用DI的存在,并封装此功能并将其隐藏在契约后面(我还要说,当SMTPClient设置在DI中注册并解析为封装实现时,它们的处理会更干净)。

qqrboqgw

qqrboqgw2#

通过依赖注入添加一个单例示例会示例化被请求类的一个 instance。静态类不能被示例化,所以你可以根据静态类的访问修饰符从其他地方访问它的方法。

fykwrbwg

fykwrbwg3#

正如你所说,单例对象可以被示例化一次,所以它是一个让对象保持活动的好地方,你可以在应用程序的生命周期中使用对象。对于mvc项目,单例对象对于每个请求都是相同的。
在方法2中,SmtpClient不需要每次都创建一个新示例并释放它。
来自msdn文档:
SmtpClient类实现汇集SMTP连接,以便可以避免为到同一服务器的每封邮件重新建立连接的开销。应用程序可能会重复使用同一SmtpClient对象将许多不同的电子邮件发送到同一SMTP服务器和许多不同的SMTP服务器。因此,无法确定应用程序何时使用完SmtpClient对象并应对其进行清理。
因此,它是单例实用程序服务的一个很好的候选者。
单例模式如下所示:

public class SmtpUtilityService : ISmtpUtilityService, IDisposable
{
    private readonly SmtpClient _smtpClient;
    
    public SmtpUtilityService()
    {
        _smtpClient = new SmtpClient([...]);
    }
    
    public async Task SendEmail(str eaddress)
    {
        await _smtpClient.SendAsync([...]);
    }

    public void Dispose()
    {
        if(_smtpClient != null)
        {
            _smtpClient.Dispose();
        }
    }
}

在Statup.cs中,将SmtpUtilityService作为单个示例添加到IServiceCollection中,SmtpClient将只示例化一次。
顺便说一下,微软不推荐使用SmtpClient(在某些平台上已过时,在其他平台上不推荐),所以不确定它是否是一个好的候选者:/
msdn smtpclient obsolete
对于你的第一个方法ConvertMeterToMiles(int mtr),它只是转换,一次一个计算,它不需要任何属性,也不需要示例,所以完整的静态类是个不错的选择。

public static class MeterHelper
{
    public static decimal ConvertMeterToMiles(int mtr)
    {
        return mtr * 0.0006213712;
    }
}

就我个人而言,我不经常使用单例,如果我需要属性,我会使用作用域或 transient 服务,如果我不需要,我会使用完整的静态类(助手)。

i1icjdpr

i1icjdpr4#

一个静态类就是一个单例类,创建一个单例类作为一个静态字段或者通过依赖注入,唯一的区别就是如何访问它,仅此而已。
但是在SmtpClient的上下文中,它是not thread-safe。实际上,文档是这样说的:

如果正在传输电子邮件,并且您再次拨打SendAsyncSend,您将收到InvalidOperationException
换句话说,你不能使用SmtpClient的同一个示例同时发送两封邮件,所以使用SmtpClient作为任何类型的单例都不是一个好主意,你最好要么让它有作用域,要么根本不使用DI,在需要的时候声明一个新的。

2w2cym1i

2w2cym1i5#

让我先回顾一下:一个静态类不能有示例,所以你使用它的方法和类名,一个单例类只能有一个示例被其他类共享。
对于一个静态类,你需要把它包含在你的代码文件中。2对于一个单例类,你将首先创建一个示例,然后把它传递给参数列表中的其他方法。
在aspnet的上下文中,由于我们将只有1个示例,我们将其创建和处置留给带有services.AddSingleton<ISingleton, Singleton>();的框架,并通过public SomeController(ISingleton singleton)将其获取到我们的控制器。当访问者点击这个控制器端点时,他们所有的请求都将不同,但随后由这个单一示例处理。aspnet将通过它们的接口确定您的控制器需要哪些单例,并只注入那些请求的单例。
无论是服务器端全局状态保持器、数据库连接器,还是电子邮件发送者,您实现的所有活动都将通过此单一示例。您可以在其中实现负载平衡器,以便可以处理请求而不会出现瓶颈。
另一方面,对于一个静态类,你会更喜欢短期的方法,因为它们将为控制器的每个请求单独运行。distance converter就是这样的方法之一。它不需要任何长的进程来完成它的工作,也不依赖于其他昂贵的资源。然而,你可能想要缓存最频繁的计算并从该高速缓存发送响应。那么将这个距离转换器转换为长时间使用资源的单例将是一个更好的主意。
因此,简而言之,根据资源的使用情况,您将更喜欢短期独立方法或具有大量昂贵操作的长期方法。
看到OP对他使用的SMTPClient有困惑,我想再添加几行。
你需要问一个问题:此客户端是否打开一个到SMTP服务器的通道并将其保留长时间使用,或者它只是通过它发送1条消息并在之后需要关闭。
一些客户端具有一次性使用的核心功能,其他客户端基于此核心行为构建,并添加预先打开的一次性连接池。核心功能类既可用作静态的、给定资源作为参数的类,也可用作单件类(如果它允许初始化连接本身以外的资源)。这两种情况都需要仅在使用时打开到SMTP服务器的通道,最后,如果它在使用后必须关闭,那么核心功能就不能作为单例使用,因为我们需要在服务的整个生命周期中保持活动。
另一方面,如果客户端使用一个连接池,那么毫无疑问它将是一个单例并且对用户体验有积极的影响。如果在project的使用环境中没有其他当前库可用,那么这里的一个旁注将是实现一个拥有这个连接池的自己的类。

k3bvogb1

k3bvogb16#

我建议使用注入的单例。从功能上来说,这几乎没有什么区别,但是注入单例在测试方面有**很大的优势。
虽然静态方法本身很容易测试,但是测试独立使用它们的代码就变得非常困难。
让我们以SendEmail(str eaddress)为例。如果我们将其实现为一个静态helper方法,那么在不创建一个真实的的SMTPClient的情况下,就不可能对使用该方法的代码进行单元测试。相反,如果我们用一个接口注入一个单例helper类,那么在测试调用SendEmail的代码时,该接口可能会被模拟。

iq3niunx

iq3niunx7#

Singleton和Static在函数性上没有区别。
在你所描述的线程环境中,唯一的问题是共享数据。如果多个线程访问相同的属性,就会出现并发问题。
现在,如果您只是使用实用程序方法,如Sum(int a, int b),它没有任何状态,就不会有任何问题。
现在,这两种情况基本上没有什么区别,除了需要注入单例之外,即使与web API有关,也没有什么特别的。
也许除了一个单例类可以继承,而静态类不能,但那是另一个主题了。

相关问题