提问人:Eric Haskins 提问时间:9/8/2008 最后编辑:ruffinEric Haskins 更新时间:10/23/2020 访问量:53217
为什么/何时应该在 .net 中使用嵌套类?还是你不应该?
Why/when should you use nested classes in .net? Or shouldn't you?
问:
在 Kathleen Dollard 2008 年的博客文章中,她提出了一个在 .net 中使用嵌套类的有趣原因。但是,她也提到 FxCop 不喜欢嵌套类。我假设编写 FxCop 规则的人并不愚蠢,所以这个立场背后一定有原因,但我没能找到它。
答:
当嵌套的类仅对封闭类有用时,请使用嵌套类。例如,嵌套类允许您编写类似(简化)的内容:
public class SortedMap {
private class TreeNode {
TreeNode left;
TreeNode right;
}
}
你可以在一个地方对你的类进行完整的定义,你不必跳过任何PIMPL的箍来定义你的类是如何工作的,外界也不需要看到你的任何实现。
如果 TreeNode 类是外部的,则必须创建所有字段或创建一堆方法来使用它。外面的世界会有另一个阶级污染他们的智力。public
get/set
评论
如果我没看错 Katheleen 的文章,她建议使用嵌套类来编写 SomeEntity.Collection 而不是 EntityCollection<SomeEntity>。在我看来,这是为您节省一些打字的有争议的方式。我很确定在现实世界中,应用程序集合在实现上会有一些差异,因此无论如何您都需要创建单独的类。我认为使用类名来限制其他类范围不是一个好主意。它污染了智能感知并加强了类之间的依赖关系。使用命名空间是控制类范围的标准方法。但是,我发现像@hazzen注释中那样使用嵌套类是可以接受的,除非您有大量的嵌套类,这是设计不佳的标志。
这取决于用法。我很少使用公共嵌套类,而是一直使用私有嵌套类。私有嵌套类可用于仅在父级内部使用的子对象。例如,如果 HashTable 类包含一个私有 Entry 对象,该对象仅在内部存储数据。
如果该类旨在由调用者(外部)使用,我通常喜欢将其设置为一个单独的独立类。
为什么要使用嵌套类? 使用嵌套类有几个令人信服的理由,其中包括:
- 这是一种对仅在一个地方使用的类进行逻辑分组的方法。
- 它增加了封装。
- 嵌套类可以生成更具可读性和可维护性的代码。
类的逻辑分组 - 如果一个类仅对另一个类有用,则将其嵌入到该类中并将两者放在一起是合乎逻辑的。嵌套这样的“帮助程序类”使它们的包更加精简。
增加封装 - 考虑两个顶级类,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();
}
}
来源: https://csharpindepth.com/Articles/Singleton
我经常使用嵌套类来隐藏实现细节。埃里克·利珀特(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
}
嵌套类的另一个尚未提及的用途是泛型类型的分离。例如,假设想要一些泛型的静态类族,这些类可以采用具有不同数量参数的方法以及其中一些参数的值,并生成具有较少参数的委托。例如,人们希望有一个静态方法,它可以接受 an 并产生 a,它将调用传递 3.5 的所提供的操作作为 ;人们可能还希望有一个静态方法,它可以接受一个 an 并产生一个 ,作为 和 作为 传递。使用泛型嵌套类,可以安排方法调用如下所示:Action<string, int, double>
String<string, int>
double
Action<string, int, double>
Action<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);
使用嵌套泛型类型可以判断哪些委托适用于整个类型描述的哪些部分。
正如 nawfal 提到的抽象工厂模式的实现,该代码可以被调整为实现基于抽象工厂模式的类集群模式。
嵌套类可用于以下需求:
- 数据分类
- 当主类的逻辑很复杂,并且您觉得需要从属对象来管理该类时
- 当你认为类的状态和存在完全取决于封闭类时
我喜欢嵌套单个类独有的异常,即。那些永远不会从任何其他地方扔下来的。
例如:
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
属性结合使用。但是,这与仅出于测试目的在内部创建私有字段相同,我认为这是糟糕的自我文档。
因此,您可能只想实现涉及低复杂性的私有嵌套类。
除了上面列出的其他原因之外,我还能想到另一个原因,不仅使用嵌套类,而且实际上使用公共嵌套类。对于那些使用共享相同泛型类型参数的多个泛型类的人来说,声明泛型命名空间的能力将非常有用。不幸的是,.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 在前面的评论中建议的分部类,您可以将类分隔为单独的嵌套文件。我建议使用 Visual Studio 扩展(如 NestIn)来支持嵌套分部类文件。这允许“命名空间”类文件也用于在类似文件夹中组织嵌套的类文件。
例如:
实体 .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
{
}
实体.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;
}
}
}
实体.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;
}
}
}
实体.IBase业务.cs
partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
public interface IBaseBusiness
{
tDataObjectList Load();
void Delete();
void Save(tDataObjectList data);
}
}
实体.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; }
}
}
用户.数据对象列表 .cs
partial class User
{
public class DataObjectList : BaseDataObjectList {}
}
用户.IBusiness.cs
partial class User
{
public interface IBusiness : IBaseBusiness {}
}
用户.IData访问.cs
partial class User
{
public interface IDataAccess : IBaseDataAccess {}
}
这些文件将在解决方案资源管理器中组织,如下所示:
User.cs
+ User.DataObject.cs
+ User.DataObjectList.cs
+ User.IBusiness.cs
+ User.IDataAccess.cs
以上是使用外部类作为泛型命名空间的简单示例。我过去构建了包含 9 个或更多类型参数的“通用命名空间”。必须使这些类型参数在九种类型之间保持同步,而所有类型都需要知道类型参数,这很繁琐,尤其是在添加新参数时。泛型命名空间的使用使该代码更易于管理和可读。
对于这种情况,是的:
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();
}
}
评论