C#中的密封方法

42

我是C#的新手。我正在学习关于Sealed关键字的内容。我已经了解了关于密封类的信息。我读到一行有关Sealed方法的内容,其中我们也可以将方法声明为Sealed。这行代码是(通过将方法声明为sealed,我们可以避免进一步重写此方法。

我创建了一个演示但不理解上述代码行和Sealed方法的用途。以下是我的代码:

using System;

namespace ConsoleApplication2
{
    class Program:MyClass
    {
        public override sealed void Test()
        {
            Console.WriteLine("My class Program");
        }
        static void Main(string[] args)
        {
            Program obj = new Program();
            obj.Test();
            Console.ReadLine();
        }
    }

    class MyClass
    {
        public virtual void Test()
        {
            Console.WriteLine("My class Test");
        }
    }


}
请告诉我为什么我们要使用密封方法(Sealed methods),以及密封方法的优点是什么。
7个回答

82

你只测试了两层继承关系,还没有达到你“进一步覆盖”方法的点。如果你将其设置为三层,你就可以看到sealed的作用:

class Base {
   public virtual void Test() { ... }
}
class Subclass1 : Base {
   public sealed override void Test() { ... }
}
class Subclass2 : Subclass1 {
   public override void Test() { ... } // Does not compile!
   // If `Subclass1.Test` was not sealed, it would've compiled correctly.
}

4
第一层可以是接口吗? - Jhonny D. Cano -Leftware-

77

一个sealed的类是一个不能成为其他更派生类的基类的类。

在一个未封闭的类中,一个sealed的方法是一个不能在此类的派生类中被覆盖的方法。

我们为什么要使用sealed的方法?sealed的方法有哪些优势?

好吧,为什么要使用虚方法?为了提供一个可以自定义一个类行为的点。那么为什么要使用sealed的方法?为了提供一个可以保证任何派生类在这个方法的行为方面没有进一步更改的点。

自定义一个类行为的点是有用的但也是危险的。因为它们允许派生类更改基类的行为。虚方法基本上允许第三方使您的类做您从未预料或测试过的事情。

我喜欢编写按照我的预期和测试所执行的代码。将方法封装允许您继续允许类的部分被重写,同时使sealed的方法具有可靠、可测试、稳定的行为,并且不能进一步自定义。


2
很好。当我的教练不知道这是什么时,我找到了你的答案。 - John Lord

22

如果您不希望任何派生类进一步覆盖您的方法,那么只能在方法上使用“sealed”。如果它们未声明为虚拟方法并且未覆盖其他虚拟方法,则方法默认为封闭的。 (在Java中,默认情况下方法是虚拟的-要实现“默认” C#行为,您必须将方法标记为final。)

个人而言,我喜欢仔细控制继承-在可能的情况下,我更喜欢整个类被封闭。但是,在某些情况下,您仍然希望允许继承,但确保某些方法不能再次被覆盖。一个用途可能是有效地模板化方法,例如用于诊断:

public sealed override void Foo(int x)
{
    Log("Foo called with argument: {0}", x);
    FooImpl(x);
    Log("Foo completed");
}

protected abstract void FooImpl(int x);

现在子类不能直接重写Foo - 他们必须要重写FooImpl,所以当其他代码调用Foo时,我们的行为将始终被执行。

当然,模板化可能是出于其他原因 - 比如强制执行某些参数验证方面的要求。

根据我的经验,密封方法并不经常使用,但我很高兴有这个能力。(我只是希望类默认是密封的,但那又是另一个话题了。)


2

密封方法

密封方法用于定义虚拟方法的重写级别。

密封关键字总是与覆盖关键字一起使用。

密封方法的实际演示。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace sealed_method
{
    class Program
    {
        public class BaseClass
        {

            public virtual void Display()
            {
                Console.WriteLine("Virtual method");
            }
        }

       public class DerivedClass : BaseClass
        {
            // Now the display method have been sealed and can;t be overridden
            public override sealed void Display()
            {
                Console.WriteLine("Sealed method");
            }
        }

       //public class ThirdClass : DerivedClass
       //{

       //    public override void Display()
       //    {
       //        Console.WriteLine("Here we try again to override display method which is not possible and will give error");
       //    }
       //}

        static void Main(string[] args)
        {

            DerivedClass ob1 = new DerivedClass();
            ob1.Display();

            Console.ReadLine();
        }
    }
}

2

现在,如果您声明Program的子类,就无法覆盖Test方法,这就是关键点。

在大多数情况下,您不需要使用封闭方法,但在开发某些插件系统的基类时,它们可能会证明自己非常有用,第三方开发人员必须扩展该类以创建自己的插件。您可以保留一些服务数据(如插件名称和是否启用),并使用封闭方法和属性来完成此操作,以便插件开发人员不会搞砸它。这是封闭成员派上用场的一种情况。


0
首先,让我们从一个定义开始;sealed是一个修饰符,如果应用于一个类上会使其不可继承,如果应用于虚方法或属性上则使它们不可重写
public sealed class A { ... }
public class B 
{
    ...
    public sealed string Property { get; set; }
    public sealed void Method() { ... }
}

它的用法示例是专门的类/方法或属性,在这些类/方法或属性潜在的改变可能导致它们停止按预期工作(例如,System.Drawing命名空间的Pens类)。

...
namespace System.Drawing
{
    //
    // Summary:
    //     Pens for all the standard colors. This class cannot be inherited.
    public sealed class Pens
    {
        public static Pen Transparent { get; }
        public static Pen Orchid { get; }
        public static Pen OrangeRed { get; }
        ...
    }
}

由于密封类无法被继承,因此它不能用作基类,进而导致抽象类无法使用sealed修饰符。同时,结构体隐式地是密封的也是需要注意的。

示例

public class BaseClass {
    public virtual string ShowMessage()
    {
        return "Hello world";
    }
    public virtual int MathematicalOperation(int x, int y)
    {
        return x + y;
    }
}
public class DerivedClass : BaseClass {
    public override int MathematicalOperation(int x, int y) 
    {
        // since BaseClass has a method marked as virtual, DerivedClass can override it's behavior
        return x - y;
    }
    public override sealed string ShowMessage()
    {
        // since BaseClass has a method marked as virtual, DerivedClass can override it's behavior but because it's sealed prevent classes that derive from it to override the method
        return "Hello world sealed";
    }
}
public class DerivedDerivedClass : DerivedClass
{
    public override int MathematicalOperation(int x, int y)
    {
        // since BaseClass has a method marked as virtual, DerivedClass can override it's behavior
        return x * y;
    }
    public override  string ShowMessage() { ... } // compile error
}
public sealed class SealedClass: BaseClass {
    public override int MathematicalOperation(int x, int y)
    {
        // since BaseClass has a method marked as virtual, DerivedClass can override it's behavior
        return x * y;
    }
    public override string ShowMessage()
    {
        // since BaseClass has a method marked as virtual, DerivedClass can override it's behavior but because it's sealed prevent classes that derive from it to override the method
        return "Hello world";
    }
}
public class DerivedSealedClass : SealedClass
{
    // compile error
}

微软文档

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/sealed


-4

C# 之所以有这个功能,是因为 Java 有一个相同的特性(final 方法)。我从未见过合法的用途。

如果您不想允许扩展,整个类应该标记为 sealed,而不仅仅是一个方法。


10
C# 之所以拥有该特性,是因为 Java 拥有它吗?我不这么认为。更可能的解释是,C# 和 Java 都拥有这个特性,是因为它是一个有用的特性。 - Eric Lippert
2
@Eric:当然,如果我们谈论的是数组协变性,那就是另一回事了 ;) - Jon Skeet
@Jon:确实,尽管当然设计决策的链条稍微不太直接。我会说CLR类型系统具有该功能是因为Java语言具有该功能,而C#具有该功能是因为CLR类型系统具有该功能。 - Eric Lippert

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