C# 中实现一个独立的partial class

今天想要分享的是的是一个关于C#的hack,其实题目也想了好久并不知道如何描述这样一个东西。大致的描述就是我们需要一个partial的类,这个类被定义在两个.cs文件中,当两个文件同时编译或者是单个编译时不会有compile error。同时这两个文件有相同的完全的capabilities(大概可以用这个词来描述)。

其实这个需求不是十分直观的,只有在特定的环境下才会有这种需求。我们在Unity的项目中,定义了一个类来处理版本号,暂且定为ClassVersion。同时我们还有一个模块A,这个模块也需要依赖ClassVersion来定义一个版本号。所以当只有模块A时我们也需要ClassVersion的定义。但是如果当模块A导入到Unity项目中时,同样的定义就会出现SymbolRedefinded。在这种环境下,最终使用了一个十分hack的方式,在不使用反射的情况下满足了需求。

尝试Partial

很显然一来想到的是C#中的Partial。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

//Unity classVersion_unity.cs
partial class ClassVersion{
public int GetMajor(){
//...
}
}


//ModuleA classVersion_moduleA.cs
partial class ClassVersion{
public int GetMajor(){
//...
}
}

这样ClassVersion在各自独立的时候是正常的工作的,但是在合并时还是有GetMajor()这个方法的重定义。如果在不用反射,不使用不同函数名的情况下,始终是会有编译错误的。同时partial method 在这里也没有办法,partial method只能实现一次,所以一定有一边缺少实现。

使用interface

尝试把class 改成interface,两个partial的Interface在去掉是现实还是会有相同method的编译报错。不过最后发现了一个常常被忽略的特性。如果有两个接口,他们有相同的方法声明,那么一个实现这两个接口的类只要实现一次这样的方法,并且也不会有编译错误,具体的代码如下。

1
2
3
4
5
6
7
8
9
10
11
12

public interface IVersionA{
int GetMajor();
}
public interface IVersionB{
int GetMajor();
}
public class ClassVersion: IVersionA,IVersionB{
public int GetMajor(){
//...
}
}

这样我们就可以避开method重定义的Compile Error了。

创建两个互补的 partial interface

Unity classVersion_unity.cs

1
2
3
4
5
6
7
8
9
10
11
12
public partial interface IVersionA{

}
public partial interface IVersionB{
int GetMajor();
}

public partial class ClassVersionUnity :IVersionA,IVersionB{
public int GetMajor(){
//...
}
}

ModuleA classVersion_moduleA.cs

1
2
3
4
5
6
7
8
9
10
11
12
13

public partial interface IVersionA{
int GetMajor();
}
public partial interface IVersionB{

}

public partial class ClassVersionModuleA :IVersionA,IVersionB{
public int GetMajor(){
//...
}
}

这样这两个.cs文件在独立编译与同时编译的情况下都不会有编译报错
在各自的环境中,只要转换为当前环境中带有实现的那个接口对象就可以直接调用方法了。

总结

这种实现方式看起来并不优雅,代码的实现还是两份,这个是无法避免的。但是避免了使用反射和强制类型转换,使用接口限定了实现,总之一个比较hack的实现方式。