什么时候适合使用C#部分类?


Answers:


422

局部类的最大用途是使代码生成器/设计人员的工作更加轻松。部分类使生成器可以简单地发出需要发出的代码,而不必处理用户对文件的编辑。用户同样可以通过拥有第二部分类来自由地用新成员为类添加注释。这为分离关注点提供了一个非常干净的框架。

观察它的更好方法是在局部类之前了解设计器的功能。WinForms设计器会在区域内吐出所有代码,并带有措辞强烈的注释,说明不要修改代码。它必须插入各种试探法以找到生成的代码,以供以后处理。现在,它可以简单地打开designer.cs文件,并高度确信它仅包含与设计者相关的代码。


70
诱惑给您-1,让我做噩梦,告诉我部分课程之前的事情有多糟糕:)
乔恩·B

9
@Jon :),我想我们都有噩梦。
JaredPar '08

18
一旦我编写了一个代码生成器,该代码生成器产生了超过36,000行(或者可能更多,我没有完全记住),并且在打开源代码时,我的编辑器也被阻止了。局部类使我能够在没有4 GB RAM的情况下查看生成的代码。
卡2010年

6
我要说的是,这是生产代码中部分类的唯一用法。尽管我接受,它可能对重构有用。
戈登·麦卡利斯特

5
@戈登-HumerGu的答案是我认为很难反对的另一个答案。部分类可以在C#中实现接口,并保持接口的成员来自类别人员明确独立非常方便:stackoverflow.com/questions/3601901/why-use-partial-classes/...
STW

260

另一个用途是拆分不同接口的实现,例如:

partial class MyClass : IF1, IF2, IF3
{
    // main implementation of MyClass
}


partial class MyClass
{
    // implementation of IF1
}

partial class MyClass
{
    // implementation of IF2
}

6
好点子!这是我之前做过但忘记的事情,但是绝对是使接口成员清晰可见的好方法(尤其是在C#中,因为VB.NET使用Implements关键字表示该方法属于接口)
STW

2
非常棒的一点是,每个接口都可以由开发人员实现,这也是轻松查找接口实现的好方法。
kokabi 2014年

3
如何通过类声明的顺序知道IF1或IF2 ..是哪一个?
kuldeep

17
很好的用法,但是不好的例子。为什么哦,为什么还要在一个子类中定义所有接口,并在其他子类中实现相同的接口呢?..
inkredibl 2015年

4
哈,我只是查询了这个问题,以查看分离接口的实现是否是部分类的标准用法。很高兴看到其他人将其视为一个好主意。我同意@inkredibl关于将接口定义与实现它的部分类放在一起的观点。

170

除了其他答案...

我发现它们作为重构神级的垫脚石很有用。如果一个类有多个职责(特别是如果它是一个很大的代码文件),那么我发现将每个职责的1x部分类添加为组织和重构代码的第一步是有益的。

这有很大帮助,因为它可以帮助使代码更具可读性,而不会实际影响执行行为。它还可以帮助确定何时易于分解责任或与其他方面紧密联系在一起。

但是-要清楚-这仍然是不好的代码,在开发结束时,您仍然希望每个类一个责任(而不是每个部分类)。这只是一块垫脚石:)


23
非常好:“ ...在开发结束时,您仍然希望每个班级承担一个责任(而不是每个部分班级……)”
kokabi 2014年

对我来说,这不是一种好的编码风格,但是会使糟糕的代码看起来更好。
themefield

我完全同意这一点。这是修复错误代码的良好垫脚石。神级是神级天气,它是否分布在多个文件中。
Jimbo

84
  1. 多个开发人员使用局部类多个开发人员可以轻松地在同一个类上工作。
  2. 代码生成器部分类主要由代码生成器用来将不同的关注点分开
  3. 局部方法使用局部类,您还可以定义局部方法,开发人员可以简单地定义方法,而其他开发人员可以实现该方法。
  4. 仅部分方法声明即使代码使用方法声明进行编译,并且如果不存在该方法的实现,编译器也可以安全地删除该代码段,并且不会发生编译时错误。

    验证点4。只需创建一个winform项目,并在Form1构造函数之后添加此行,然后尝试编译代码

    partial void Ontest(string s);

以下是实现部分类时要考虑的几点:-

  1. 在局部类的每个部分中使用局部关键字。
  2. 局部类的每个部分的名称应该相同,但是局部类的每个部分的源文件名称可以不同。
  3. 局部类的所有部分都应在同一命名空间中。
  4. 局部类的每个部分都应位于相同的程序集或DLL中,换句话说,您不能在来自不同类库项目的源文件中创建局部类。
  5. 局部类的每个部分必须具有相同的可访问性。(即:私人,公共或受保护)
  6. 如果您在局部类上继承一个类或接口,则该局部类的所有部分都将继承它。
  7. 如果部分类的一部分被密封,则整个类将被密封。
  8. 如果部分类的一部分是抽象的,则整个类将被视为抽象类。

1
编辑从Entity Framework创建的部分类是否有问题?我希望更改一些从表创建的类名。
MarceloBarbosa 2015年

我也有同样的担忧。我想从Code First的角度/方法/实现的角度来了解部分类在构建实体框架将用于构建数据库的模型时所具有的“如果有”的好处。
Chef_Code

@JimBalter请通过此链接msdn.microsoft.com/zh-cn/library/6b0scde8(v=vs.110).aspx。这说明如果没有实现,当前的编译器将删除这段代码,并且不会收到编译时错误。
hellowahab '16

如果您在局部类上继承一个类或接口,则该局部类的所有部分都将继承它。
红豌豆

56

一个很好的用途是将生成的代码与属于同一类的手写代码分开。

例如,由于LINQ to SQL使用局部类,因此您可以编写某些功能(例如多对多关系)的自己的实现,并且重新生成代码时,这些自定义代码不会被覆盖。

WinForms代码也是如此。Designer生成的所有代码都放在一个您通常不会触摸的文件中。您的手写代码位于另一个文件中。这样,当您在Designer中进行更改时,所做的更改不会被删除。


21

确实,Partial Class用于自动代码生成,一种用途是维护一个大型的类文件,该文件可能包含数千行代码。您永远不会知道您的班级可能会以一万行结尾,并且您不想创建一个具有不同名称的新班级。

public partial class Product
{
    // 50 business logic embedded in methods and properties..
}

public partial class Product
{
    // another 50 business logic embedded in methods and properties..
}
//finally compile with product.class file.

另一个可能的用途是,不止一个开发人员可以在同一个类上工作,因为他们存储在不同的位置。人们可能会笑,但您永远不知道有时候它可能很少。

产品1.cs

public partial class Product
{
    //you are writing the business logic for fast moving product
}

产品2.cs

public partial class Product
{
    // Another developer writing some business logic...
}

希望有道理!


13

局部类跨越多个文件。

如何在C#类声明上使用partial修饰符?

使用部分类,您可以将一个类物理上分成多个文件。这通常是由代码生成器完成的。

对于普通的C#类,您不能在同一项目的两个单独的文件中声明一个类。但是使用partial修饰符,您可以。

如果一个文件通常被编辑而另一个文件是机器生成的或很少被编辑的,则这很有用。

这里有一个例子可以说明:

class Program
{
    static void Main()
    {
        A.A1();
        A.A2();
    }
}

文件A1.cs的内容:C#

using System;

partial class A
{
    public static void A1()
    {
        Console.WriteLine("A1");
    }
}

文件A2.cs的内容:C#

using System;

partial class A
{
    public static void A2()
    {
        Console.WriteLine("A2");
    }
}

输出:

A1
A2

这里需要部分。

如果删除partial修饰符,将收到包含以下文本的错误:

[名称空间' <global namespace>'已经包含' '的定义A

小费:

要解决此问题,您可以使用partial关键字,也可以更改类名之一。

C#编译器如何处理部分类?

如果您反汇编上述程序(使用IL Disassembler),则会看到文件A1.cs和A2.cs被删除。您会发现存在A类。

类A将在同一代码块中包含方法A1和A2。这两个类别合并为一个。

A1.cs和A2.cs的编译结果:C#

internal class A
{
    // Methods
    public static void A1()
    {
        Console.WriteLine("A1");
    }

    public static void A2()
    {
        Console.WriteLine("A2");
    }
}

摘要

  • 局部类可以简化某些C#编程情况。
  • 创建Windows Forms / WPF程序时,它们通常在Visual Studio中使用。
  • 机器生成的C#代码是独立的。
  • 或者,您可以在此处找到整个说明。

2
很好的例子,有据可查。
Chef_Code

1
这是遵循imo 的最简单的解释。
Yusha

12

在使用大型班级或团队工作时,尽可能使所有内容保持整洁,您可以编辑而不覆盖(或始终提交更改)


11

局部类的主要用途是生成代码。如果您查看WPF(Windows Presentation Foundation)网络,则使用标记(XML)定义UI。该标记被编译成部分类。您可以使用自己的部分类来填充代码。


8

如果您有足够大的类无法进行有效的重构,则将其分为多个文件有助于使事情井井有条。

例如,如果您有一个包含讨论论坛和产品系统的网站的数据库,并且不想创建两个不同的提供程序类(请注意,与代理类不是同一件事),则可以在不同的文件中创建一个局部类,例如

MyProvider.cs-核心逻辑

MyProvider.Forum.cs-与论坛有关的方法

MyProvider.Product.cs-产品方法

这是使事情井井有条的另一种方式。

而且,正如其他人所说,这是向已生成的类添加方法的唯一方法,而不会冒着下次重新生成该类时销毁添加项的风险。这与模板生成(T4)代码,ORM等一起派上用场。


2
我主张将partial's作为重构的垫脚石(我的回答的全部内容),但不建议将它们作为编写干净代码的实际解决方案。如果将部分课程与该课程的其他问题完全区分开,那么为什么不花额外的精力将其提升为独立的课程呢?
STW 2010年

@STW:可能是创建了许多对象实例并将其用于各种任务。将各种任务划分为不同的类将需要跟踪哪些实例用于哪些任务,这可能比仅在源模块之间移动代码块要大得多。
supercat 2010年

4
@supercat-我完全理解,但是应该清理这种意大利面。完全清除那种类型的代码会给我带来很多伤疤,并且永远也不会提倡将其抛在脑后。保证这些类型的混乱会不断产生问题,与仅仅忽略问题相比,长期收益是巨大的。像这样的代码不会“闻”到,就像垃圾堆一样。
STW,2010年

1
@supercat-我试图用“如果将部分内容与其他问题完全分开...这是一件小事”。避免缠结的痛苦通常可以节省很多长期维护费用,即使不是Rogaine
STW 2010年

2
顺便说一句,这些天我一直在尝试修改Roslyn,并且它广泛使用了部分类。Roslyn中的很多主要类被定义为多个文件中的部分类。Roslyn是由至少我认为是非常聪明的C#程序员的人编写的。
RenniePet

8

替代预编译器指令。

如果使用预编译器指令(即#IF DEBUG),那么最终会得到一些看上去与实际的Release代码混杂在一起的粗糙代码。

您可以创建一个单独的包含该代码的局部类,然后将整个局部类包装在一个指令中,或者忽略该代码文件不发送给编译器(实际上是这样做的)。


Monogame使用此策略。
Zamboni


6

我看到的另一个用途是

扩展有关数据访问逻辑的大型抽象类,

我有各种文件名为Post.cs,Comment.cs,Pages.cs ...

in Post.cs 

public partial class XMLDAO :BigAbstractClass
{
// CRUD methods of post..
}


in Comment.cs 

public partial class XMLDAO :BigAbstractClass
{
// CRUD methods of comment..
}

in Pages.cs 

public partial class XMLDAO :BigAbstractClass
{
// CRUD methods of Pages..
}

6

多数人指出,此方法partial仅应用于具有生成的代码文件的类或接口。我不同意,这就是原因。

例如,让我们看一下C#System.Math类...那是class。我不会尝试将70多种方法全部塞入同一代码文件中。维持这将是一场噩梦。

将每种数学方法放入项目的单独的部分类文件中,并将所有代码文件放入项目中的Math文件夹中,将会大大简化组织。

对于具有大量不同功能的许多其他类,同样/可能适用。例如,用于管理PrivateProfile API的类可能会因为在单个项目文件夹中被分成一组干净的部分类文件而受益。

就个人而言,我还将大多数人称为“ helper”或“ utility”的类划分为每个方法或方法功能组的单独的部分文件。例如,在一个项目中,字符串帮助器类具有近50种方法。即使使用区域,那也将是一个很长的笨拙的代码文件。对于每种方法,使用单独的部分类文件进行维护非常容易。

在执行此操作时,我会小心使用局部类,并在整个项目中保持所有代码文件的布局一致。例如,将任何类公共枚举和类私有成员放入Common.cs或文件夹中类似名称的文件中,而不是将它们散布到整个文件中,除非它们仅特定于它们所包含的部分文件。

请记住,将类拆分为单独的文件时,您还将失去使用文本编辑器拆分器栏的能力,该栏使您可以同时查看当前文件的两个不同部分。


4

局部类使得仅通过添加源文件就可以向适当设计的程序中添加功能。例如,可以设计一个文件导入程序,以便可以通过添加处理文件的模块来添加不同类型的已知文件。例如,主文件类型转换器可以包括一个小类:

局部公共类zzFileConverterRegistrar
    事件寄存器(ByVal mainConverter作为zzFileConverter)
    子registerAll(ByVal mainConverter为zzFileConverter)
        RaiseEvent寄存器(mainConverter)
    结束子
最终班

每个希望注册一种或多种类型的文件转换器的模块都可以包含以下内容:

局部公共类zzFileConverterRegistrar
    私有子寄存器Gif(ByVal mainConverter as zzFileConverter)处理我。
        mainConverter.RegisterConverter(“ GIF”,GifConverter.NewFactory))
    结束子
最终班

请注意,主文件转换器类不是“公开的”,它只是公开了一个附加模块可以连接的小桩类。命名冲突有轻微的风险,但是如果每个加载项模块的“注册”例程均根据其处理的文件类型进行命名,则它们可能不会造成问题。如果有人担心这样的事情,可以将GUID粘贴在注册子例程的名称中。

编辑/附录 明确地说,这样做的目的是提供一种方法,使各种不同的类可以让主程序或类知道它们。主文件转换器使用zzFileConverterRegistrar要做的唯一一件事就是创建它的一个实例,并调用registerAll方法,该方法将触发Register事件。任何想要挂接到该事件的模块都可以执行任意代码以响应该事件(这就是整个思路),但是除了定义名称与其他名称匹配的方法外,不正确地扩展zzFileConverterRegistrar类也无法实现任何模块功能。一个不正确编写的扩展名肯定可以破坏另一个不正确编写的扩展名,但是对于那些不希望其扩展名被破坏的人来说,解决方案就是简单地正确编写它。

在不使用部分类的情况下,可以在主文件转换器类中的某处有一些代码,如下所示:

  RegisterConverter(“ GIF”,GifConvertor.NewFactory)
  RegisterConverter(“ BMP”,BmpConvertor.NewFactory)
  RegisterConverter(“ JPEG”,JpegConvertor.NewFactory)

但是添加另一个转换器模块将需要进入转换器代码的这一部分,并将新的转换器添加到列表中。不再需要使用部分方法-所有转换器将自动包含在内。


它可以工作,但是一个简单的插件系统来动态加载这些模块会更好,并且可以消除模块相互破坏的风险(它可以在运行时加载模块,而无需重新编译)
STW 2010年

如果模块对zz_类不执行任何操作(钩住Register事件并调用例程进行注册),则可以将模块彼此损坏的风险降到最低。您认为该插件不存在哪些风险?如果希望最终用户“插入”新模块,则插件非常有用。但是有时候,有些人希望将所有功能都放在一个exe文件中。能够方便地插入源文件而不必手动添加对新添加文件的引用。
supercat

1
风险完全在您的行中包含。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。如果您的用例是将它们编译进来(完全有效,只需要出于良心的考虑),那么为什么不将模块定义在单独的类中并实现某些IModule接口呢?
STW 2010年

听起来像是“聪明”坏了的情况。这些不是“模块”,它们是单个类,在编译时将许多行为和职责汇总为一个类。有很多的这样做的更好的方式-你可以使用反射来扫描编译的程序集为其实现类IModule,你可以使用插件框架状MEF(只是其中之一),等等等等
STW

3

局部类最近有助于源代码控制,其中多个开发人员将一个文件添加到文件中,而新方法又添加到文件的同一部分中(由Resharper自动执行)。

这些对git的推送导致合并冲突。我发现没有办法告诉合并工具将新方法用作完整的代码块。

在这方面,部分类允许开发人员坚持使用他们文件的版本,我们可以稍后手动将它们合并回去。

例子-

  • MainClass.cs-保存字段,构造函数等
  • MainClass1.cs-开发人员在实现时的新代码
  • MainClass2.cs-是他们的新代码的另一个开发人员类。

3

MSDN

1.在编译时,部分类型定义的属性会合并。例如,考虑以下声明:

[SerializableAttribute]
partial class Moon { }

[ObsoleteAttribute]
partial class Moon { }

它们等效于以下声明:

[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }

以下是所有部分类型定义的合并:

  • XML注释

  • 介面

  • 泛型参数属性

  • 类属性

  • 成员

2.另一件事,嵌套的部分类也可以是部分的:

partial class ClassWithNestedClass
{
    partial class NestedClass { }
}

partial class ClassWithNestedClass
{
    partial class NestedClass { }
}

1

这里列出了部分类的一些优点。

您可以将UI设计代码和业务逻辑代码分开,以使其易于阅读和理解。例如,您正在使用Visual Studio开发Web应用程序,并添加一个新的Web表单,那么就有两个源文件“ aspx.cs”和“ aspx.designer.cs”。这两个文件具有相同的类,但带有partial关键字。“ .aspx.cs”类具有业务逻辑代码,而“ aspx.designer.cs”具有用户界面控件定义。

使用自动生成的源代码时,可以将代码添加到类中,而无需重新创建源文件。例如,您正在使用LINQ to SQL并创建一个DBML文件。现在,当您拖放表格时,它将在designer.cs中创建一个局部类,并且所有表格列均在该类中具有属性。您需要在此表中添加更多列以绑定到UI网格上,但是您不想在数据库表中添加新列,因此可以为此类创建一个单独的源文件,该文件对该列具有新属性,并且它将成为部分课程。因此,这确实会影响数据库表与DBML实体之间的映射,但是您可以轻松地获得一个额外的字段。这意味着您可以自己编写代码,而不会弄乱系统生成的代码。

多个开发人员可以同时编写该类的代码。

您可以通过压缩大型类来更好地维护您的应用程序。假设您有一个具有多个接口的类,因此您可以根据接口实现来创建多个源文件。易于理解和维护实现的接口,源文件在该接口上具有部分类。



1

我知道这个问题确实很老,但我只想补充一下部分课程。

我个人使用部分类的原因之一是在为程序(尤其是状态机)创建绑定时。

例如,OpenGL是一个状态机,有是都可以但是在全球范围改变,以我的经验结合类似于OpenGL的情况下有这么多的方法什么方法,类可以很容易地超过10K LOC。

局部类将为我分解这种情况,帮助我快速找到方法。


0

引入部分类主要是为了帮助代码生成器,因此,我们(用户)最终不会在每次重新生成时失去对ASP.NET的.designer.cs类(例如ASP.NET的.designer.cs类)的所有工作/更改。代码LINQ,EntityFrameworks,ASP.NET使用部分类生成代码,因此我们可以利用部分类和方法安全地添加或更改这些生成代码的逻辑,但是在使用部分类向生成的代码添加内容之前,请务必格外小心如果我们中断构建会更容易,但是如果引入运行时错误则会更糟。有关更多详细信息,请参见http://www.4guysfromrolla.com/articles/071509-1.aspx


0

我注意到在用法中找不到的两种用法。

分组课程项目

一些开发人员使用注释来分隔类的不同“部分”。例如,一个团队可能使用以下约定:

public class MyClass{  
  //Member variables
  //Constructors
  //Properties
  //Methods
}

对于部分类,我们可以更进一步,并将这些部分拆分为单独的文件。按照惯例,团队可以在每个文件后缀对应的部分。因此,在上面,我们将具有以下内容:MyClassMembers.cs,MyClassConstructors.cs,MyClassProperties.cs,MyClassMethods.cs。

正如其他答案所暗示的,在这种情况下,是否值得拆分班级可能取决于班级的规模。如果很小,将所有内容都放在一个大师班中可能会更容易。但是,如果这些部分中的任何一个太大,则可以将其内容移到单独的局部类中,以使主类保持整洁。在这种情况下,一个约定可能是在标题部分之后留下诸如“请参阅部分类”之类的注释,例如:

//Methods - See partial class

管理使用语句/命名空间的范围

这可能很少发生,但是您要使用的库中的两个函数之间可能存在名称空间冲突。在单个类中,最多可以为其中一个使用using子句。对于另一个,您需要一个完全限定的名称或别名。对于部分类,由于每个名称空间和using语句列表都不相同,因此可以将两组函数分成两个单独的文件。


有一些机制可以解决名称空间冲突,例如,使用using Library1 = The.Namespace.You.Needglobal::Root.Of.Namespace
fjch1997 '19

是的,我认为这是一个较弱的用例。但是不必完全限定名称会更好一些。与使用分部类的原因相比,这更是一个不错的意外副作用。
Colm Bhandal
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.