在Kathleen Dollard 的 2008 年博客文章中,她提出了在 .net 中使用嵌套类的有趣理由。然而,她也提到 FxCop 不喜欢嵌套类。我假设编写 FxCop 规则的人并不愚蠢,所以这个立场背后一定有推理,但我一直找不到。
14 回答
当您嵌套的类仅对封闭类有用时,请使用嵌套类。例如,嵌套类允许您编写类似(简化)的内容:
public class SortedMap {
private class TreeNode {
TreeNode left;
TreeNode right;
}
}
您可以在一个地方对您的类进行完整的定义,您不必跳过任何 PIMPL 箍来定义您的类的工作方式,并且外部世界不需要看到您的任何实现。
如果 TreeNode 类是外部的,您要么必须创建所有字段public
,要么创建一堆get/set
方法来使用它。外面的世界会有另一个阶级污染他们的智能。
为什么使用嵌套类?使用嵌套类有几个令人信服的理由,其中包括:
- 这是一种对仅在一个地方使用的类进行逻辑分组的方法。
- 它增加了封装。
- 嵌套类可以产生更易读和可维护的代码。
类的逻辑分组——如果一个类只对另一个类有用,那么将它嵌入该类并将两者放在一起是合乎逻辑的。嵌套这样的“帮助类”使它们的包更加精简。
增加封装——考虑两个顶级类 A 和 B,其中 B 需要访问 A 的成员,否则这些成员将被声明为私有。通过将类 B 隐藏在类 A 中,可以将 A 的成员声明为私有的,并且 B 可以访问它们。此外,B 本身可以对外界隐藏。<- 这不适用于 C# 的嵌套类实现,这只适用于 Java。
更具可读性、可维护性的代码——在顶级类中嵌套小类可以使代码更接近使用它的位置。
完全惰性和线程安全的单例模式
public sealed class Singleton
{
Singleton()
{
}
public static Singleton Instance
{
get
{
return Nested.instance;
}
}
class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}
internal static readonly Singleton instance = new Singleton();
}
}
这取决于使用情况。我很少使用公共嵌套类,但一直使用私有嵌套类。私有嵌套类可用于仅在父对象内部使用的子对象。例如,如果 HashTable 类包含私有 Entry 对象以仅在内部存储数据。
如果该类是由调用者(外部)使用的,我通常喜欢将其设为一个单独的独立类。
除了上面列出的其他原因之外,还有一个原因我可以想到的不仅是使用嵌套类,而且实际上是公共嵌套类。对于那些使用共享相同泛型类型参数的多个泛型类的人来说,声明泛型命名空间的能力将非常有用。不幸的是,.Net(或至少 C#)不支持泛型命名空间的想法。所以为了实现同样的目标,我们可以使用泛型类来实现同样的目标。采用以下与逻辑实体相关的示例类:
public class BaseDataObject
<
tDataObject,
tDataObjectList,
tBusiness,
tDataAccess
>
where tDataObject : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
where tBusiness : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataAccess : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}
public class BaseDataObjectList
<
tDataObject,
tDataObjectList,
tBusiness,
tDataAccess
>
:
CollectionBase<tDataObject>
where tDataObject : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
where tBusiness : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataAccess : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}
public interface IBaseBusiness
<
tDataObject,
tDataObjectList,
tBusiness,
tDataAccess
>
where tDataObject : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
where tBusiness : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataAccess : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}
public interface IBaseDataAccess
<
tDataObject,
tDataObjectList,
tBusiness,
tDataAccess
>
where tDataObject : BaseDataObject<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataObjectList : BaseDataObjectList<tDataObject, tDataObjectList, tBusiness, tDataAccess>, new()
where tBusiness : IBaseBusiness<tDataObject, tDataObjectList, tBusiness, tDataAccess>
where tDataAccess : IBaseDataAccess<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
}
我们可以通过使用通用命名空间(通过嵌套类实现)来简化这些类的签名:
public
partial class Entity
<
tDataObject,
tDataObjectList,
tBusiness,
tDataAccess
>
where tDataObject : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObject
where tDataObjectList : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObjectList, new()
where tBusiness : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseBusiness
where tDataAccess : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseDataAccess
{
public class BaseDataObject {}
public class BaseDataObjectList : CollectionBase<tDataObject> {}
public interface IBaseBusiness {}
public interface IBaseDataAccess {}
}
然后,通过使用 Erik van Brakel 在之前的评论中建议的部分类,您可以将这些类分离到单独的嵌套文件中。我建议使用像 NestIn 这样的 Visual Studio 扩展来支持嵌套部分类文件。这允许“命名空间”类文件也可以用于在文件夹中组织嵌套的类文件。
例如:
实体.cs
public
partial class Entity
<
tDataObject,
tDataObjectList,
tBusiness,
tDataAccess
>
where tDataObject : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObject
where tDataObjectList : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.BaseDataObjectList, new()
where tBusiness : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseBusiness
where tDataAccess : Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>.IBaseDataAccess
{
}
Entity.BaseDataObject.cs
partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
public class BaseDataObject
{
public DataTimeOffset CreatedDateTime { get; set; }
public Guid CreatedById { get; set; }
public Guid Id { get; set; }
public DataTimeOffset LastUpdateDateTime { get; set; }
public Guid LastUpdatedById { get; set; }
public
static
implicit operator tDataObjectList(DataObject dataObject)
{
var returnList = new tDataObjectList();
returnList.Add((tDataObject) this);
return returnList;
}
}
}
Entity.BaseDataObjectList.cs
partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
public class BaseDataObjectList : CollectionBase<tDataObject>
{
public tDataObjectList ShallowClone()
{
var returnList = new tDataObjectList();
returnList.AddRange(this);
return returnList;
}
}
}
Entity.IBaseBusiness.cs
partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
public interface IBaseBusiness
{
tDataObjectList Load();
void Delete();
void Save(tDataObjectList data);
}
}
Entity.IBaseDataAccess.cs
partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
public interface IBaseDataAccess
{
tDataObjectList Load();
void Delete();
void Save(tDataObjectList data);
}
}
然后,Visual Studio 解决方案资源管理器中的文件将按如下方式组织:
Entity.cs
+ Entity.BaseDataObject.cs
+ Entity.BaseDataObjectList.cs
+ Entity.IBaseBusiness.cs
+ Entity.IBaseDataAccess.cs
您将实现如下所示的通用命名空间:
用户.cs
public
partial class User
:
Entity
<
User.DataObject,
User.DataObjectList,
User.IBusiness,
User.IDataAccess
>
{
}
用户数据对象.cs
partial class User
{
public class DataObject : BaseDataObject
{
public string UserName { get; set; }
public byte[] PasswordHash { get; set; }
public bool AccountIsEnabled { get; set; }
}
}
User.DataObjectList.cs
partial class User
{
public class DataObjectList : BaseDataObjectList {}
}
用户.IBusiness.cs
partial class User
{
public interface IBusiness : IBaseBusiness {}
}
用户.IDataAccess.cs
partial class User
{
public interface IDataAccess : IBaseDataAccess {}
}
文件将在解决方案资源管理器中按如下方式组织:
User.cs
+ User.DataObject.cs
+ User.DataObjectList.cs
+ User.IBusiness.cs
+ User.IDataAccess.cs
上面是一个使用外部类作为泛型命名空间的简单示例。过去,我构建了包含 9 个或更多类型参数的“通用名称空间”。必须在所有需要知道类型参数的九种类型之间保持这些类型参数同步是很乏味的,尤其是在添加新参数时。通用命名空间的使用使代码更易于管理和可读。
如果我对 Katheleen 的文章理解正确,她建议使用嵌套类来编写 SomeEntity.Collection 而不是 EntityCollection<SomeEntity>。在我看来,这是一种有争议的方式来节省你的打字时间。我很确定在现实世界中的应用程序集合在实现上会有一些差异,所以无论如何你都需要创建单独的类。我认为使用类名来限制其他类的范围不是一个好主意。它污染了智能感知并加强了类之间的依赖关系。使用命名空间是控制类范围的标准方法。但是,我发现像 @hazzen 评论中使用嵌套类是可以接受的,除非您有大量嵌套类,这是设计不良的标志。
我经常使用嵌套类来隐藏实现细节。Eric Lippert 在这里回答的一个例子:
abstract public class BankAccount
{
private BankAccount() { }
// Now no one else can extend BankAccount because a derived class
// must be able to call a constructor, but all the constructors are
// private!
private sealed class ChequingAccount : BankAccount { ... }
public static BankAccount MakeChequingAccount() { return new ChequingAccount(); }
private sealed class SavingsAccount : BankAccount { ... }
}
通过使用泛型,这种模式变得更好。有关两个很酷的示例,请参见此问题。所以我最终写了
Equality<Person>.CreateComparer(p => p.Id);
代替
new EqualityComparer<Person, int>(p => p.Id);
我也可以有一个通用列表,Equality<Person>
但不是EqualityComparer<Person, int>
var l = new List<Equality<Person>>
{
Equality<Person>.CreateComparer(p => p.Id),
Equality<Person>.CreateComparer(p => p.Name)
}
然而
var l = new List<EqualityComparer<Person, ??>>>
{
new EqualityComparer<Person, int>>(p => p.Id),
new EqualityComparer<Person, string>>(p => p.Name)
}
不可能。这就是嵌套类继承父类的好处。
另一种情况(性质相同 - 隐藏实现)是当您想让一个类的成员(字段、属性等)仅对单个类可访问时:
public class Outer
{
class Inner //private class
{
public int Field; //public field
}
static inner = new Inner { Field = -1 }; // Field is accessible here, but in no other class
}
嵌套类尚未提及的另一个用途是泛型类型的分离。例如,假设想要拥有一些通用的静态类家族,它们可以采用具有不同数量参数的方法以及其中一些参数的值,并生成具有较少参数的委托。例如,一个人希望有一个静态方法,它可以接受一个Action<string, int, double>
并产生一个String<string, int>
,它将调用提供的动作,将 3.5 作为double
; 可能还希望有一个静态方法,它可以采用 anAction<string, int, double>
并产生 an ,作为 the和作为 theAction<string>
传递。使用通用嵌套类,可以安排方法调用类似于:7
int
5.3
double
MakeDelegate<string,int>.WithParams<double>(theDelegate, 3.5);
MakeDelegate<string>.WithParams<int,double>(theDelegate, 7, 5.3);
或者,因为可以推断出每个表达式中的后者类型,即使前者不能:
MakeDelegate<string,int>.WithParams(theDelegate, 3.5);
MakeDelegate<string>.WithParams(theDelegate, 7, 5.3);
使用嵌套的泛型类型可以判断哪些委托适用于整个类型描述的哪些部分。
嵌套类可用于以下需求:
- 数据分类
- 当主类的逻辑比较复杂,感觉需要从属对象来管理类时
- 当您认为类的状态和存在完全取决于封闭类时
我喜欢嵌套单个类独有的异常,即。那些从未从任何其他地方扔过的东西。
例如:
public class MyClass
{
void DoStuff()
{
if (!someArbitraryCondition)
{
// This is the only class from which OhNoException is thrown
throw new OhNoException(
"Oh no! Some arbitrary condition was not satisfied!");
}
// Do other stuff
}
public class OhNoException : Exception
{
// Constructors calling base()
}
}
这有助于保持您的项目文件整洁,而不是充满一百个粗短的小异常类。
请记住,您需要测试嵌套类。如果它是私有的,您将无法单独对其进行测试。
不过,您可以将其与InternalsVisibleTo
属性结合使用。但是,这与仅出于测试目的在内部创建私有字段相同,我认为这是错误的自我记录。
因此,您可能只想实现复杂性较低的私有嵌套类。
是的,对于这种情况:
class Join_Operator
{
class Departamento
{
public int idDepto { get; set; }
public string nombreDepto { get; set; }
}
class Empleado
{
public int idDepto { get; set; }
public string nombreEmpleado { get; set; }
}
public void JoinTables()
{
List<Departamento> departamentos = new List<Departamento>();
departamentos.Add(new Departamento { idDepto = 1, nombreDepto = "Arquitectura" });
departamentos.Add(new Departamento { idDepto = 2, nombreDepto = "Programación" });
List<Empleado> empleados = new List<Empleado>();
empleados.Add(new Empleado { idDepto = 1, nombreEmpleado = "John Doe." });
empleados.Add(new Empleado { idDepto = 2, nombreEmpleado = "Jim Bell" });
var joinList = (from e in empleados
join d in departamentos on
e.idDepto equals d.idDepto
select new
{
nombreEmpleado = e.nombreEmpleado,
nombreDepto = d.nombreDepto
});
foreach (var dato in joinList)
{
Console.WriteLine("{0} es empleado del departamento de {1}", dato.nombreEmpleado, dato.nombreDepto);
}
}
}
根据我对这个概念的理解,当类在概念上相互关联时,我们可以使用这个特性。我的意思是它们中的一些是我们业务中的一个完整项目,例如存在于 DDD 世界中的实体,它们帮助聚合根对象完成其业务逻辑。
为了澄清,我将通过一个例子来展示这一点:
假设我们有两个类,比如 Order 和 OrderItem。在 order 类中,我们将管理所有 orderItems,在 OrderItem 中,我们保存有关单个订单的数据以进行澄清,您可以看到以下类:
class Order
{
private List<OrderItem> _orderItems = new List<OrderItem>();
public void AddOrderItem(OrderItem line)
{
_orderItems.Add(line);
}
public double OrderTotal()
{
double total = 0;
foreach (OrderItem item in _orderItems)
{
total += item.TotalPrice();
}
return total;
}
// Nested class
public class OrderItem
{
public int ProductId { get; set; }
public int Quantity { get; set; }
public double Price { get; set; }
public double TotalPrice() => Price * Quantity;
}
}
class Program
{
static void Main(string[] args)
{
Order order = new Order();
Order.OrderItem orderItem1 = new Order.OrderItem();
orderItem1.ProductId = 1;
orderItem1.Quantity = 5;
orderItem1.Price = 1.99;
order.AddOrderItem(orderItem1);
Order.OrderItem orderItem2 = new Order.OrderItem();
orderItem2.ProductId = 2;
orderItem2.Quantity = 12;
orderItem2.Price = 0.35;
order.AddOrderItem(orderItem2);
Console.WriteLine(order.OrderTotal());
ReadLine();
}
}