C# 为什么在抽象类中,内部字段比受保护字段更可取?

C# 为什么在抽象类中,内部字段比受保护字段更可取?,c#,.net,access-modifiers,C#,.net,Access Modifiers,我有一个包含串行端口的设备的抽象类: public abstract class SerialDevice { // serial port (should this be protected, internal, or protected internal?) protected SerialPort _serialPort; // The serial port has some shared methods. public void Open()

我有一个包含串行端口的设备的抽象类:

public abstract class SerialDevice
{
    // serial port (should this be protected, internal, or protected internal?)
    protected SerialPort _serialPort;

    // The serial port has some shared methods.
    public void Open()
    {
        _serialPort.Open();
    }
}
派生类也使用串行端口,但我不希望端口在派生类之外可见

public class Widget : SerialDevice
{
    // The serial port has some widget-specific functionality.
    public void UniqueCommand()
    {
        _serialPort.WriteLine("Hello world.");
    }
}
当我编译时,我得到以下警告:,它说字段应该是一个隐藏的实现细节。好的,我可以使用一个受保护的属性来代替,保护我的类不受破坏内容的更改的影响

但它继续建议字段是私有的或内部的。声明说,使串行端口“受保护”只允许在派生类中访问它,而使其“内部”则允许在整个程序集中访问它。那么,为什么“内部”是可以接受的,而“受保护”则不是?受保护的访问不是比内部访问更不可见吗


相关问题:

在某种程度上,受保护的字段比内部字段更可见,因为如果扩展类,则可以访问程序集外部的字段,但内部将其限制在定义字段的一个程序集上。

在某种程度上,受保护的字段比内部字段更可见,因为如果扩展类,但内部将其限制为定义字段的一个程序集。

为了实现封装,我们不应该将字段直接暴露于外部,甚至暴露于我们自己的程序集中的类

相反,我们可以有一个内部getter方法,这样只有程序集中的类才能访问、使用它,但不能更改它的引用

如果我们更进一步,希望抽象/基类只能由我们自己的程序集中的类扩展:

通过将基类公开,并将所有构造函数指定为内部构造函数,我们可以确保只有我们自己的程序集中的类才能扩展它

public abstract class SerialDevice
{
    private SerialPort _serialPort;

    internal SerialPort GetSerialPort()
    {
        return _serialPort;
    }

    // We define our base class public,
    // but also define all the constructors internal
    // Therefore, no class outside our assembly extend it
    // but we still can expose sub classes (like Widget) to the outside
    internal SerialDevice()
    {

    }

    // The serial port has some shared methods.
    // These methods are still visible from the outside
    public void Open()
    {
        _serialPort.Open();
    }
}
以及


为了实现封装,我们不应该将字段直接公开给外部,甚至是我们自己的程序集中的类

相反,我们可以有一个内部getter方法,这样只有程序集中的类才能访问、使用它,但不能更改它的引用

如果我们更进一步,希望抽象/基类只能由我们自己的程序集中的类扩展:

通过将基类公开,并将所有构造函数指定为内部构造函数,我们可以确保只有我们自己的程序集中的类才能扩展它

public abstract class SerialDevice
{
    private SerialPort _serialPort;

    internal SerialPort GetSerialPort()
    {
        return _serialPort;
    }

    // We define our base class public,
    // but also define all the constructors internal
    // Therefore, no class outside our assembly extend it
    // but we still can expose sub classes (like Widget) to the outside
    internal SerialDevice()
    {

    }

    // The serial port has some shared methods.
    // These methods are still visible from the outside
    public void Open()
    {
        _serialPort.Open();
    }
}
以及


Oguz的答案很好,但我决定使用属性而不是方法。因此,我的解决方案看起来是这样的,并且似乎符合最佳实践

  • 抽象类是公共的,因此程序集的其余部分可以看到它
  • 抽象类的构造函数是内部的,因此不能在程序集外部继承
  • 串口“port”是一个属性,而不是一个字段,因此可以稍后在抽象类中对其进行更改,而无需中断任何派生类
  • 属性“Port”受保护,因此除了从基类派生的类之外,无法看到它

Oguz的答案很好,但我决定使用属性而不是方法。因此,我的解决方案看起来是这样的,并且似乎符合最佳实践

  • 抽象类是公共的,因此程序集的其余部分可以看到它
  • 抽象类的构造函数是内部的,因此不能在程序集外部继承
  • 串口“port”是一个属性,而不是一个字段,因此可以稍后在抽象类中对其进行更改,而无需中断任何派生类
  • 属性“Port”受保护,因此除了从基类派生的类之外,无法看到它

您的类是公共的,有一个公共构造函数(因为您的问题没有显示构造函数,我们应该假设它有一个默认构造函数),所以任何程序集中的任何类都可以扩展您的类并可以访问受保护的成员。这就是它抱怨的原因。@OguzOzgul,这是有道理的。我不能将抽象类设为私有类,否则会出现错误CS1527(命名空间中定义的元素不能显式声明为private、protected、protected internal或private protected)。它应该是内部的还是其他的?如果我把它设为内部的,我会得到CS0060(不一致的可访问性:基类“SerialDevice”的可访问性不如类“Widget”)。解决方案是什么?让SerialDevice公开,这就是我们如何公开内部类,将内部抽象类或基类扩展到外部世界。只需将其设置为内部构造函数。将_serialPort保持在内部您的类是公共的,有一个公共构造函数(因为您的问题没有显示构造函数,我们应该假设它有一个默认构造函数),因此任何程序集中的任何类都可以扩展您的类并访问受保护的成员。这就是它抱怨的原因。@OguzOzgul,这是有道理的。我不能将抽象类设为私有类,否则会出现错误CS1527(命名空间中定义的元素不能显式声明为private、protected、protected internal或private protected)。它应该是内部的还是其他的?如果我把它设为内部的,我会得到CS0060(不一致的可访问性:基类“SerialDevice”的可访问性不如类“Widget”)。解决方案是什么?让SerialDevice公开,这就是我们如何公开内部类,将内部抽象类或基类扩展到外部世界。只需将其设置为内部构造函数。将_serialPort保持在内部
public abstract class SerialDevice
{
    // Constructor
    internal SerialDevice() {}

    // serial port
    protected SerialPort Port { get; } = new SerialPort();

    // The serial port has some shared methods.
    public void Open()
    {
        Port.Open();
    }
}
public class Widget : SerialDevice
{
    // The serial port has some widget-specific functionality.
    public void UniqueCommand()
    {
        Port.WriteLine("Hello world.");
    }
}