在C#中使用内部类

时间:2009-04-29 21:55:11

标签: c# inner-classes

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

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

使用公共继承的内部类的抽象类也是不好的做法吗?

9 个答案:

答案 0 :(得分:23)

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

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

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

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

    在大型项目中,您会惊讶地发现自己最初构建一个强耦合组件的频率是因为它的第一个或主要目的使它看起来合乎逻辑 - 但除非您有非常好的或技术上的理由来锁定它然后将它隐藏起来,然后暴露这个类几乎没有什么害处,以便其他组件可以消耗它。

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

答案 1 :(得分:18)

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

错误: ListView.ListViewItemCollection collection = new ListView.ListViewItemCollection();

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

关于你的大班:通常有必要将这样的东西分成更小的类,每个类都有一个特定的功能。最初很难将其分解,但我预测它会让你的生活更轻松......

答案 2 :(得分:9)

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

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

答案 3 :(得分:8)

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

通过这样做,您可以通过editing the project file获得更多概述,使文件组与Windows窗体中的设计器类一样。我想我已经看过一个Visual Studio插件,可以自动为你做这个,但我不记得在哪里。

修改
经过一番观察,我找到了用于执行此操作的Visual Studio加载项,称为VSCommands

答案 4 :(得分:6)

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

您可以使用partial类来拆分主类和嵌套类。当你这样做时,建议你恰当地命名文件,以便明显发生了什么。

// 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模式。

答案 5 :(得分:3)

我个人希望每个文件都有一个类,内部类作为该文件的一部分。我认为内部类通常(几乎总是)是私有的,并且是类的实现细节。将它们放在一个单独的文件中会让人感到困惑,IMO。

在这种情况下,使用代码区域来包装内部类并隐藏它们的详细信息对我来说非常有效,并且使文件难以使用。代码区域使内部类保持“隐藏”,因为它是一个私有的实现细节,对我来说没问题。

答案 6 :(得分:2)

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

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

答案 7 :(得分:0)

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

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

答案 8 :(得分: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);
}