在Kathleen Dollard's recent blog post中,她提出了一个在.net中使用嵌套类的有趣理由。但是,她还提到FxCop不喜欢嵌套类。我假设编写FxCop规则的人并不愚蠢,所以必须有理由支持这个职位,但我一直无法找到它。
答案 0 :(得分:91)
当您要嵌套的类仅对封闭类有用时,请使用嵌套类。例如,嵌套类允许您编写类似(简化)的内容:
public class SortedMap {
private class TreeNode {
TreeNode left;
TreeNode right;
}
}
您可以在一个地方完整地定义您的课程,您不必跳过任何PIMPL箍来定义您的课程的工作方式,外部世界不需要查看您的实施内容。< / p>
如果TreeNode类是外部的,则您必须创建所有字段public
或使用一堆get/set
方法来使用它。外面的世界会有另一个阶级污染他们的智力感知。
答案 1 :(得分:15)
为什么要使用嵌套类? 使用嵌套类有几个令人信服的理由,其中包括:
类的逻辑分组 - 如果一个类只对另一个类有用,那么将它嵌入该类并将两者保持在一起是合乎逻辑的。嵌套这样的“辅助类”使得它们的包更加简化。
增加封装 - 考虑两个顶级类A和B,其中B需要访问A的成员,否则这些成员将被声明为私有。通过将类B隐藏在类A中,可以将A的成员声明为私有,并且B可以访问它们。另外,B本身可以对外界隐藏。&lt; - 这不适用于C#的嵌套类实现,这只适用于Java。
更易读,可维护的代码 - 在顶级类中嵌套小类会使代码更接近于使用它的位置。
答案 2 :(得分:9)
完全懒惰且线程安全的单例模式
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();
}
}
答案 3 :(得分:5)
这取决于用法。我很少会使用Public嵌套类,但始终使用Private嵌套类。私有嵌套类可用于仅在父级内部使用的子对象。例如,如果HashTable类包含仅在内部存储数据的私有Entry对象。
如果该类要由调用者(外部)使用,我通常喜欢将它作为一个单独的独立类。
答案 4 :(得分:5)
除了上面列出的其他原因之外,还有一个原因,我不仅可以考虑使用嵌套类,还可以考虑使用公共嵌套类。对于那些使用共享相同泛型类型参数的多个泛型类的人来说,声明泛型命名空间的能力非常有用。不幸的是,.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扩展来支持嵌套部分类文件。这允许&#34;命名空间&#34;类文件也可用于组织文件夹中的嵌套类文件。
例如:
Entity.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
您将实现通用命名空间,如下所示:
User.cs
public
partial class User
:
Entity
<
User.DataObject,
User.DataObjectList,
User.IBusiness,
User.IDataAccess
>
{
}
User.DataObject.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 {}
}
User.IBusiness.cs
partial class User
{
public interface IBusiness : IBaseBusiness {}
}
User.IDataAccess.cs
partial class User
{
public interface IDataAccess : IBaseDataAccess {}
}
文件将在解决方案资源管理器中按如下方式组织:
User.cs
+ User.DataObject.cs
+ User.DataObjectList.cs
+ User.IBusiness.cs
+ User.IDataAccess.cs
以上是使用外部类作为通用名称空间的简单示例。我已经构建了通用命名空间&#34;过去包含9个或更多类型参数。必须在所有需要知道类型参数的九种类型中保持这些类型参数同步是繁琐的,特别是在添加新参数时。通用名称空间的使用使代码更易于管理和阅读。
答案 5 :(得分:3)
如果我理解Katheleen的文章,她建议使用嵌套类来编写SomeEntity.Collection而不是EntityCollection&lt; SomeEntity取代。在我看来,为你节省一些打字是有争议的方式。我很确定在现实世界的应用程序集合中实现会有一些不同,所以无论如何你都需要创建单独的类。我认为使用类名限制其他类范围不是一个好主意。它污染了intellisense并增强了类之间的依赖关系。使用命名空间是控制类范围的标准方法。但是我发现在@hazzen注释中使用嵌套类是可以接受的,除非你有大量的嵌套类,这是设计糟糕的标志。
答案 6 :(得分:1)
嵌套类尚未提及的另一个用途是泛型类型的隔离。例如,假设有人希望拥有一些通用的静态类系列,这些系列可以采用具有不同数量参数的方法,以及某些参数的值,并生成具有较少参数的委托。例如,一个人希望有一个静态方法可以取Action<string, int, double>
并产生一个String<string, int>
,它将调用所提供的动作作为double
传递3.5;一个人也可能希望有一个静态方法,可以采用Action<string, int, double>
并产生Action<string>
,将7
作为int
和5.3
作为{ {1}}。使用通用嵌套类,可以安排将方法调用类似于:
double
或者,因为可以推断出每个表达式中的后者类型,即使前者不能:
MakeDelegate<string,int>.WithParams<double>(theDelegate, 3.5);
MakeDelegate<string>.WithParams<int,double>(theDelegate, 7, 5.3);
使用嵌套的泛型类型可以告诉哪些委托适用于整体类型描述的哪些部分。
答案 7 :(得分:1)
嵌套类可用于满足以下需求:
答案 8 :(得分:0)
我经常使用嵌套类来隐藏实现细节。 An example from Eric Lippert's answer here:
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 { ... }
}
使用泛型,这种模式会变得更好。有两个很酷的例子,请参阅this question。所以我最终写了
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
}
答案 9 :(得分:0)
由于nawfal提到了抽象工厂模式的实现,该代码可以被实现为基于抽象工厂模式的Class Clusters pattern。
答案 10 :(得分:0)
我喜欢嵌套单个类独有的异常,即。那些从未被任何其他地方抛出的东西。
例如:
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()
}
}
这有助于保持项目文件整洁,并且不会充满一百个简短的异常类。
答案 11 :(得分:0)
请记住,您需要测试嵌套类。如果它是私有的,您将无法单独测试它。
你可以把它变成内部的,in conjunction with the InternalsVisibleTo
attribute。但是,这与将私有字段内部仅用于测试目的相同,我认为这是不好的自我记录。
因此,您可能只希望实现涉及低复杂性的私有嵌套类。
答案 12 :(得分:0)
在这种情况下是
merged_df = df[df['type'] == 0].merge(df[df['type'] == 1], on='time')
merged_df['diff'] = merged_df['x_x'] - merged_df['x_y']
答案 13 :(得分:0)
基于我对这个概念的理解 当类在概念上相互关联时,我们可以使用此功能。 我的意思是,其中有些像DDD世界中存在的实体一样是我们业务中完整的一项,可帮助聚合根对象完成其业务逻辑。
为了明确起见,我将通过一个示例进行展示:
想象一下,我们有两个类,例如Order和OrderItem。 在订单类中,我们将管理所有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();
}
}