Why/when should you use nested classes in .net? Or shouldn't you?
在Kathleen Dollard的2008年博客文章中,她提出了一个有趣的理由在.net中使用嵌套类。 但是,她还提到FxCop不喜欢嵌套类。 我假设编写FxCop规则的人并不愚蠢,因此在该职位后面必须有推理,但我找不到它。
当您要嵌套的类仅对封闭类有用时,请使用嵌套类。例如,嵌套类允许您编写类似(简化)的内容:
1 2 3 4 5 6
| public class SortedMap {
private class TreeNode {
TreeNode left;
TreeNode right;
}
} |
您可以在一个地方对类进行完整定义,而不必跳过任何PIMPL箍来定义类的工作方式,并且外界也无需查看任何实现。
如果TreeNode类是外部类,则必须将所有字段都设为public或使用一堆get/set方法来使用它。外部世界将有另一类污染他们的智力。
从Sun的Java教程中:
为什么要使用嵌套类?
使用嵌套类有许多令人信服的原因,其中包括:
-
这是一种对仅在一个地方使用的类进行逻辑分组的方法。
-
它增加了封装。
-
嵌套类可以导致更具可读性和可维护性的代码。
类的逻辑分组-如果一个类仅对其他一个类有用,则将其嵌入该类并将两者保持在一起是合乎逻辑的。嵌套此类"帮助程序类"可使它们的程序包更加简化。
增加的封装-考虑两个顶级类A和B,其中B需要访问A的成员,否则将其声明为私有。通过将类B隐藏在类A中,可以将A的成员声明为私有,而B可以访问它们。另外,B本身可以对外界隐藏。 s> <-这不适用于C#的嵌套类的实现,仅适用于Java。
更具可读性和可维护性的代码-将顶级类嵌套在小类中可以使代码更靠近使用位置。
完全惰性和线程安全的单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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();
}
} |
来源:http://www.yoda.arachsys.com/csharp/singleton.html
除了上面列出的其他原因外,还有一个原因使我不仅想到使用嵌套类,而且实际上可以使用公共嵌套类。对于那些使用共享相同泛型类型参数的多个泛型类的人来说,声明泛型名称空间的能力将非常有用。不幸的是,.Net(或至少C#)不支持通用名称空间的想法。因此,为了实现相同的目标,我们可以使用泛型类来实现相同的目标。采取以下与逻辑实体相关的示例类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| 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>
{
} |
我们可以使用通用名称空间(通过嵌套类实现)来简化这些类的签名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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扩展来支持嵌套部分类文件。这允许"命名空间"类文件也可以用于以类似方式在文件夹中组织嵌套的类文件。
例如:
Entity.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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
1 2 3 4 5 6 7 8 9 10 11
| partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
public interface IBaseBusiness
{
tDataObjectList Load();
void Delete();
void Save(tDataObjectList data);
}
} |
Entity.IBaseDataAccess.cs
1 2 3 4 5 6 7 8 9 10 11
| partial class Entity<tDataObject, tDataObjectList, tBusiness, tDataAccess>
{
public interface IBaseDataAccess
{
tDataObjectList Load();
void Delete();
void Save(tDataObjectList data);
}
} |
然后,Visual Studio解决方案资源管理器中的文件将组织如下:
1 2 3 4 5
| Entity.cs
+ Entity.BaseDataObject.cs
+ Entity.BaseDataObjectList.cs
+ Entity.IBaseBusiness.cs
+ Entity.IBaseDataAccess.cs |
然后您将实现通用名称空间,如下所示:
User.cs
1 2 3 4 5 6 7 8 9 10 11 12
| public
partial class User
:
Entity
<
User.DataObject,
User.DataObjectList,
User.IBusiness,
User.IDataAccess
>
{
} |
User.DataObject.cs
1 2 3 4 5 6 7 8 9 10 11
| 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
1 2 3 4 5 6
| partial class User
{
public class DataObjectList : BaseDataObjectList {}
} |
User.IBusiness.cs
1 2 3 4 5 6
| partial class User
{
public interface IBusiness : IBaseBusiness {}
} |
User.IDataAccess.cs
1 2 3 4 5 6
| partial class User
{
public interface IDataAccess : IBaseDataAccess {}
} |
这些文件将在解决方案资源管理器中按如下方式组织:
1 2 3 4 5
| User.cs
+ User.DataObject.cs
+ User.DataObjectList.cs
+ User.IBusiness.cs
+ User.IDataAccess.cs |
上面是使用外部类作为通用名称空间的简单示例。我过去建立了包含9个或更多类型参数的"通用名称空间"。必须使所有类型参数之间的所有九种类型参数都保持同步是乏味的,尤其是在添加新参数时。通用名称空间的使用使该代码更加易于管理和可读。
这取决于用法。我很少使用公共嵌套类,但一直都使用私有嵌套类。私有嵌套类可用于仅在父级内部使用的子对象。例如,如果HashTable类包含私有Entry对象以仅在内部存储数据,则为该示例。
如果该类是供调用者(外部)使用的,则我通常喜欢将其作为单独的独立类。
如果我理解Katheleen的文章正确,她建议使用嵌套类来编写SomeEntity.Collection而不是EntityCollection 。我认为这是为您节省输入时间的有争议的方法。我非常确定,在现实世界中,应用程序集合在实现上会有所不同,因此无论如何您都需要创建单独的类。我认为使用类名来限制其他类范围不是一个好主意。它会污染智能感知并增强类之间的依赖关系。使用名称空间是控制类范围的标准方法。但是我发现使用@hazzen注释中的嵌套类是可以接受的,除非您有大量的嵌套类,这表明设计不好。
嵌套类可用于以下需求:
数据分类
当主类的逻辑很复杂并且您感觉需要下级对象来管理类时
当您知道类的状态和存在完全取决于封闭的类时
嵌套类尚未提及的另一个用途是泛型类型的隔离。例如,假设一个人希望拥有一些静态类的通用类,这些类可以采用带有各种参数的方法以及其中一些参数的值,并生成带有较少参数的委托。例如,一个人希望拥有一个静态方法,该方法可以采用Action并产生一个String,该方法将调用提供的动作并传递3.5作为double;可能还希望有一种静态方法,该方法可以采用Action并产生Action,并通过7作为int和5.3作为double。使用通用嵌套类,可以安排方法调用如下:
1 2
| MakeDelegate<string,int>.WithParams<double>(theDelegate, 3.5);
MakeDelegate<string>.WithParams<int,double>(theDelegate, 7, 5.3); |
或者,因为即使前者不能,也可以推断出每个表达式中的后者类型:
1 2
| MakeDelegate<string,int>.WithParams(theDelegate, 3.5);
MakeDelegate<string>.WithParams(theDelegate, 7, 5.3); |
使用嵌套的泛型类型可以分辨出哪些委托适用于整个类型描述的哪些部分。
我经常使用嵌套类来隐藏实现细节。埃里克·利珀特(Eric Lippert)的答案示例如下:
1 2 3 4 5 6 7 8 9 10
| 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 { ... }
} |
使用泛型时,这种模式会变得更好。有关两个很酷的示例,请参见此问题。所以我最后写了
1
| Equality<Person>.CreateComparer(p => p.Id); |
代替
1
| new EqualityComparer<Person, int>(p => p.Id); |
我也可以有Equality的通用列表,但不能有EqualityComparer
1 2 3 4 5
| var l = new List<Equality<Person>>
{
Equality<Person>.CreateComparer(p => p.Id),
Equality<Person>.CreateComparer(p => p.Name)
} |
在哪里
1 2 3 4 5
| var l = new List<EqualityComparer<Person, ??>>>
{
new EqualityComparer<Person, int>>(p => p.Id),
new EqualityComparer<Person, string>>(p => p.Name)
} |
不可能。这就是从父类继承嵌套类的好处。
另一种情况(性质相同-隐藏实现)是当您只想让一个类的成员(字段,属性等)可访问时:
1 2 3 4 5 6 7 8 9
| 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
} |
是的,在这种情况下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| 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);
}
}
} |
请记住,您需要测试嵌套类。如果它是私有的,那么您将无法单独对其进行测试。
但是,您可以将其与InternalsVisibleTo属性一起设置为内部。但是,这与仅出于测试目的而将私有字段设为内部相同,我认为这是不好的自我文档。
因此,您可能只想实现涉及低复杂度的私有嵌套类。
我喜欢嵌套单个类唯一的异常,即。从来没有从其他地方扔过的东西。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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()
}
} |
这有助于使您的项目文件保持整洁,并且不会充满一百个粗俗的小异常类。
正如nawfal提到的"抽象工厂"模式的实现一样,可以扩展该代码以实现基于"抽象工厂"模式的"类集群"模式。
|