28

关于 C# 中内部类的使用和结构的最佳实践是什么。

例如,如果我有一个非常大的基类和两个大的内部类,我应该将它们拆分为单独的(部分类)代码文件还是将它们保留为一个非常大且笨重的代码文件?

拥有一个带有公共继承内部类的抽象类也是不好的做法吗?

4

9 回答 9

26

通常我为以下两个目的之一保留内部类:

  1. 从其父类派生的公共类,其中父类是具有一个或多个抽象方法的抽象基实现,每个子类都是服务于特定实现的实现。在阅读框架设计和指南后,我看到这被标记为“避免”,但是我在类似于枚举的场景中使用它——尽管这也可能给人留下不好的印象

  2. 内部类是私有的,是业务逻辑单元,或者以其他方式与父类紧密耦合,当它们被任何其他类消费或使用时,它们会从根本上被破坏。

对于所有其他情况,我尝试将它们保持在与其使用者/逻辑父级相同的名称空间和相同的可访问性级别——通常使用的名称比“主”类不太友好。

在大型项目中,您会惊讶地发现自己最初构建强耦合组件的频率仅仅是因为它的首要或主要目的使它看起来合乎逻辑——但是除非您有很好的或技术上的理由来锁定和隐藏它从视觉上看,暴露该类以使其他组件可以使用它几乎没有害处。

编辑请记住,即使我们谈论的是子类,它们也应该或多或少是设计良好且松散耦合的组件。即使它们是私有的并且对外部世界不可见,在类之间保持最小的“表面积”也将大大简化代码的可维护性,以便将来扩展或更改。

于 2009-04-29T22:12:55.323 回答
18

我手头没有这本书,但框架设计指南建议使用public内部类,只要客户不必引用类名。private内部类很好:没有人会注意到这些。

坏的: ListView.ListViewItemCollection collection = new ListView.ListViewItemCollection();

好的: listView.Items.Add(...);

关于你的大类:通常值得将这样的东西分成更小的类,每个类都有一个特定的功能。一开始很难打破它,但我预测它会让你以后的生活更轻松......

于 2009-04-29T22:06:59.957 回答
9

通常内部类应该是私有的并且只能由包含它们的类使用。如果它们的内部类非常大,则表明它们应该是自己的类。

通常当你有一个大的内部类时,这是因为内部类与它的包含类紧密耦合,并且需要访问它的私有方法。

于 2009-04-29T22:00:56.273 回答
8

我认为这是相当主观的,但我可能会通过使“主机”类部分化,将它们分成单独的代码文件。

通过这样做,您可以通过编辑项目文件以使文件组就像 Windows 窗体中的设计器类一样获得更多概览。我想我已经看到了一个 Visual Studio 插件,它会自动为您执行此操作,但我不记得在哪里。

编辑:
经过一番查看,我发现了用于执行此操作的 Visual Studio 加载项,称为 VSCommands

于 2009-04-29T22:01:19.703 回答
6

仅关于如何构造这样的野兽...

您可以使用分部类来拆分主类和嵌套类。当您这样做时,建议您适当地命名文件,以便清楚地知道发生了什么。

// main class in file Outer.cs
namespace Demo
{
  public partial class Outer
  {
     // Outer class
  }
}

// nested class in file Outer.Nested1.cs
namespace Demo
{
  public partial class Outer
  {
    private class Nested1
    {
      // Nested1 details
    }
  }
}

以同样的方式,您经常在自己的文件中看到(显式)接口。例如Outer.ISomeInterface.cs,而不是编辑器默认设置#region它们。

您的项目文件结构然后开始看起来像

   /Project/Demo/ISomeInterface.cs
   /Project/Demo/Outer.cs
   /Project/Demo/Outer.Nested1.cs
   /Project/Demo/Outer.ISomeInterface.cs

通常,当我们这样做时,它是针对 Builder 模式的变体。

于 2009-04-29T22:44:47.510 回答
3

我个人喜欢每个文件有一个类,内部类作为该文件的一部分。我相信内部类通常(几乎总是)应该是私有的,并且是类的实现细节。将它们放在单独的文件中会使事情变得混乱,IMO。

在这种情况下,使用代码区域包裹内部类并隐藏它们的细节对我来说效果很好,并且可以防止文件难以处理。代码区域保持内部类“隐藏”,因为它是一个私有的实现细节,这对我来说很好。

于 2009-04-29T22:08:49.857 回答
2

我个人使用内部类来封装一些仅在类内部使用的概念和操作。这样我就不会污染该类的非公共 api 并保持 api 干净和紧凑。

您可以利用部分类将这些内部类的定义移动到不同的文件中,以便更好地进行组织。VS 不会自动为您分组部分类文件,除了一些模板化项目,如 ASP.NET、WinForm 表单等。您需要编辑项目文件并在其中进行一些更改。您可以查看其中的现有分组之一以了解它是如何完成的。我相信有一些宏允许您在解决方案资源管理器中为您分组部分类文件。

于 2009-04-29T22:17:31.630 回答
0

在我看来,内部类,如果需要的话,应该保持很小,并且只由该类在内部使用。如果您在 .NET 框架上使用 Relfector,您会看到它们大量用于此目的。

如果您的内部类变得太大,我肯定会以某种方式将它们移到单独的类/代码文件中,如果只是为了可维护性。我必须支持一些现有的代码,有人认为在内部类中使用内部类是个好主意。它导致了一个运行 4 到 5 层深度的内部类层次结构。不用说代码是难以理解的,需要很长时间才能理解你在看什么。

于 2012-02-17T09:15:47.723 回答
0

这里看一个嵌套类的实际示例,可以让您了解它们的使用(添加了一些单元测试)

namespace CoreLib.Helpers
{
    using System;
    using System.Security.Cryptography;

    public static class Rnd
    {
        private static readonly Random _random = new Random();

        public static Random Generator { get { return _random; } }

        static Rnd()
        {
        }

        public static class Crypto
        {
            private static readonly RandomNumberGenerator _highRandom = RandomNumberGenerator.Create();

            public static RandomNumberGenerator Generator { get { return _highRandom; } }

            static Crypto()
            {
            }

        }

        public static UInt32 Next(this RandomNumberGenerator value)
        {
            var bytes = new byte[4];
            value.GetBytes(bytes);

            return BitConverter.ToUInt32(bytes, 0);
        }
    }
}

[TestMethod]
public void Rnd_OnGenerator_UniqueRandomSequence()
{
    var rdn1 = Rnd.Generator;
    var rdn2 = Rnd.Generator;
    var list = new List<Int32>();
    var tasks = new Task[10];
    for (var i = 0; i < 10; i++)
    {
        tasks[i] = Task.Factory.StartNew((() =>
        {
            for (var k = 0; k < 1000; k++)
            {
                lock (list)
                {
                    list.Add(Rnd.Generator.Next(Int32.MinValue, Int32.MaxValue));
                }
            }
        }));
    }
    Task.WaitAll(tasks);
    var distinct = list.Distinct().ToList();
    Assert.AreSame(rdn1, rdn2);
    Assert.AreEqual(10000, list.Count);
    Assert.AreEqual(list.Count, distinct.Count);
}

[TestMethod]
public void Rnd_OnCryptoGenerator_UniqueRandomSequence()
{
    var rdn1 = Rnd.Crypto.Generator;
    var rdn2 = Rnd.Crypto.Generator;
    var list = new ConcurrentQueue<UInt32>();
    var tasks = new Task[10];
    for (var i = 0; i < 10; i++)
    {
        tasks[i] = Task.Factory.StartNew((() =>
        {
            for (var k = 0; k < 1000; k++)
            {
                    list.Enqueue(Rnd.Crypto.Generator.Next());
            }
        }));
    }
    Task.WaitAll(tasks);
    var distinct = list.Distinct().ToList();
    Assert.AreSame(rdn1, rdn2);
    Assert.AreEqual(10000, list.Count);
    Assert.AreEqual(list.Count, distinct.Count);
}
于 2015-05-07T23:45:39.110 回答