为什么/什么时候应该在.net 中使用嵌套类? 还是不应该?

Kathleen Dollard 2008年的博客文章中,她提出了在。网。但是,她也提到 FxCop 不喜欢嵌套类。我假设那些制定 FxCop 规则的人并不愚蠢,所以这种立场背后一定有原因,但我一直找不到。

50705 次浏览

Use a nested class when the class you are nesting is only useful to the enclosing class. For instance, nested classes allow you to write something like (simplified):

public class SortedMap {
private class TreeNode {
TreeNode left;
TreeNode right;
}
}

You can make a complete definition of your class in one place, you don't have to jump through any PIMPL hoops to define how your class works, and the outside world doesn't need to see anything of your implementation.

If the TreeNode class was external, you would either have to make all the fields public or make a bunch of get/set methods to use it. The outside world would have another class polluting their intellisense.

If I understand Katheleen's article right, she proposes to use nested class to be able to write SomeEntity.Collection instead of EntityCollection< SomeEntity>. In my opinion it's controversial way to save you some typing. I'm pretty sure that in real world application collections will have some difference in implementations, so you will need to create separate class anyway. I think that using class name to limit other class scope is not a good idea. It pollutes intellisense and strengthen dependencies between classes. Using namespaces is a standard way to control classes scope. However I find that usage of nested classes like in @hazzen comment is acceptable unless you have tons of nested classes which is a sign of bad design.

It depends on the usage. I rarely would ever use a Public nested class but use Private nested classes all of the time. A private nested class can be used for a sub-object that is intended to be used only inside the parent. An example of this would be if a HashTable class contains a private Entry object to store data internally only.

If the class is meant to be used by the caller (externally), I generally like making it a separate standalone class.

From Sun's Java Tutorial:

Why Use Nested Classes? There are several compelling reasons for using nested classes, among them:

  • It is a way of logically grouping classes that are only used in one place.
  • It increases encapsulation.
  • Nested classes can lead to more readable and maintainable code.

Logical grouping of classes—If a class is useful to only one other class, then it is logical to embed it in that class and keep the two together. Nesting such "helper classes" makes their package more streamlined.

Increased encapsulation—Consider two top-level classes, A and B, where B needs access to members of A that would otherwise be declared private. By hiding class B within class A, A's members can be declared private and B can access them. In addition, B itself can be hidden from the outside world. <- This doesn't apply to C#'s implementation of nested classes, this only applies to Java.

More readable, maintainable code—Nesting small classes within top-level classes places the code closer to where it is used.

Fully Lazy and thread-safe singleton pattern

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();
}
}

source: https://csharpindepth.com/Articles/Singleton

I often use nested classes to hide implementation detail. 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 pattern becomes even better with use of generics. See this question for two cool examples. So I end up writing

Equality<Person>.CreateComparer(p => p.Id);

instead of

new EqualityComparer<Person, int>(p => p.Id);

Also I can have a generic list of Equality<Person> but not EqualityComparer<Person, int>

var l = new List<Equality<Person>>
{
Equality<Person>.CreateComparer(p => p.Id),
Equality<Person>.CreateComparer(p => p.Name)
}

where as

var l = new List<EqualityComparer<Person, ??>>>
{
new EqualityComparer<Person, int>>(p => p.Id),
new EqualityComparer<Person, string>>(p => p.Name)
}

is not possible. That's the benefit of nested class inheriting from parent class.

Another case (of the same nature - hiding implementation) is when you want to make a class's members (fields, properties etc) accessible only for a single class:

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
}

Another use not yet mentioned for nested classes is the segregation of generic types. For example, suppose one wants to have some generic families of static classes that can take methods with various numbers of parameters, along with values for some of those parameters, and generate delegates with fewer parameters. For example, one wishes to have a static method which can take an Action<string, int, double> and yield a String<string, int> which will call the supplied action passing 3.5 as the double; one may also wish to have a static method which can take an an Action<string, int, double> and yield an Action<string>, passing 7 as the int and 5.3 as the double. Using generic nested classes, one can arrange to have the method invocations be something like:

MakeDelegate<string,int>.WithParams<double>(theDelegate, 3.5);
MakeDelegate<string>.WithParams<int,double>(theDelegate, 7, 5.3);

or, because the latter types in each expression can be inferred even though the former ones can't:

MakeDelegate<string,int>.WithParams(theDelegate, 3.5);
MakeDelegate<string>.WithParams(theDelegate, 7, 5.3);

Using the nested generic types makes it possible to tell which delegates are applicable to which parts of the overall type description.

As nawfal mentioned implementation of Abstract Factory pattern, that code can be axtended to achieve Class Clusters pattern which is based on Abstract Factory pattern.

The nested classes can be used for following needs:

  1. Classification of the data
  2. When the logic of the main class is complicated and you feel like you require subordinate objects to manage the class
  3. When you that the state and existence of the class fully depends on the enclosing class

I like to nest exceptions that are unique to a single class, ie. ones that are never thrown from any other place.

For example:

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()
}
}

This helps keep your project files tidy and not full of a hundred stubby little exception classes.

Bear in mind that you'll need to test the nested class. If it is private, you won't be able to test it in isolation.

You could make it internal, though, in conjunction with the InternalsVisibleTo attribute. However, this would be the same as making a private field internal only for testing purposes, which I consider bad self-documentation.

So, you may want to only implement private nested classes involving low complexity.

In addition to the other reasons listed above, there is one more reason that I can think of not only to use nested classes, but in fact public nested classes. For those who work with multiple generic classes that share the same generic type parameters, the ability to declare a generic namespace would be extremely useful. Unfortunately, .Net (or at least C#) does not support the idea of generic namespaces. So in order to accomplish the same goal, we can use generic classes to fulfill the same goal. Take the following example classes related to a logical entity:

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>
{
}

We can simplify the signatures of these classes by using a generic namespace (implemented via nested classes):

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 {}


}

Then, through the use of partial classes as suggested by Erik van Brakel in an earlier comment, you can separate the classes into separate nested files. I recommend using a Visual Studio extension like NestIn to support nesting the partial class files. This allows the "namespace" class files to also be used to organize the nested class files in a folder like way.

For example:

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);
}


}

The files in the visual studio solution explorer would then be organized as such:

Entity.cs
+   Entity.BaseDataObject.cs
+   Entity.BaseDataObjectList.cs
+   Entity.IBaseBusiness.cs
+   Entity.IBaseDataAccess.cs

And you would implement the generic namespace like the following:

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 {}


}

And the files would be organized in the solution explorer as follows:

User.cs
+   User.DataObject.cs
+   User.DataObjectList.cs
+   User.IBusiness.cs
+   User.IDataAccess.cs

The above is a simple example of using an outer class as a generic namespace. I've built "generic namespaces" containing 9 or more type parameters in the past. Having to keep those type parameters synchronized across the nine types that all needed to know the type parameters was tedious, especially when adding a new parameter. The use of generic namespaces makes that code far more manageable and readable.

yes for this case:

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);
}
}
}

Base on my understanding of this concept we could use this feature when classes are related to each other conceptually. I mean some of them are complete one Item in our business like entities that exist in the DDD world that help to an aggregate root object to complete its business logic.

In order to clarify I'm going to show this via an example:

Imagine that we have two classes like Order and OrderItem. In order class, we are going to manage all orderItems and in OrderItem we are holding data about a single order for clarification, you can see below classes:

 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();
}




}