c#:在两个不同的上下文中重用部分类定义

时间:2014-10-05 00:16:24

标签: c# namespaces partial-classes

我正在尝试重用常见的部分类定义来定义几个不同的类。这是说明所需结果的概念(非工作)代码:

namespace common {
    // Here is the part of the class which I want to re-use
    // literally in two different contexts
    partial class A {
        public void f() { Console.WriteLine(s); }
    }
}

namespace context1 {
    using common; // using as #include
    partial class A {
        String s = "contetx1";
    }
}

namespace context2 {
    using common; // using as #include
    partial class A {
        String s = "context2";
    }
}

class Program {
    static void Main(string[] args) {
        context1.A a1 = new context1.A();
        context2.A a2 = new context2.A();
        a1.f();
        a2.f();
        Console.ReadKey();
    }
}

context1和context2中的代码是自动生成的。我想在自动生成的类中使用相同的手动编写的代码(命名空间'通用'以上)。目前我使用自动生成器脚本复制公共块,但是想知道在c#中有更优雅的方法吗?

澄清 - 上面的代码只是一个例子,我理解为什么它不起作用(不能跨命名空间分割部分类定义)。另外,虽然在上面的例子中,我可以只有一个基本的公共类,实际上它确实需要是同一个类,我用部分定义来实现。

---后来补充---

好的,在阅读完答案之后,我意识到我的问题的答案是" No",我感谢你们确认我担心使用部分课程并不容易。让我试着详细说明我是如何提出这样的问题的。也许你会指出一些简单而优雅的解决方案。想象一下,有一个巨大的类描述了一些硬件模型以及模拟它的方法,它是从一些硬件描述中自动生成的。类似的东西:

partial class CPU1 : GenericCPU {
    ...
    partial class register123 { /*...fields and methods...*/ }
    ...
    partial class alu758 {
        /*...thousand fields and methods with partial hook method definitions...*/
        partial void start_get_data_hook();
        partial void finish_get_data_hook();
        void get_data() {
            start_get_data_hook();
            /*... some alu758-specific code for getting input data...*/
            finish_get_data_hook();
        }
        ...
    }
    ...
    void override simulate(); // Assume GenericCPU has virtual "simulate()"
}

假设没有任何其他东西可以模拟一些CPU(或其他)。部分钩子方法和调用得到优化,每个人都很开心。现在,我希望能够轻松地定义自动生成文件之外的钩子,我需要钩子才能访问它们各自的类字段和方法。每次我想使用其中一个钩子时,为钩子需要的数据定义一个特殊的接口是不可能的。假设我想在CPU1.alu758.get_data()的开头和结尾处做一些特殊的事情,它对部分类和方法来说非常容易:

partial class CPU1 {
    partial class alu758 {
        /* some more fields which the hooks use */
        partial void start_get_data_hook() { /* do some preparation stuff */ }
        partial void finish_get_data_hook() { /* do some finalization stuff */ }
    }
}

虽然只有一个CPU1类可以完美运行。但是,让我们说有10个自动生成的CPU类CPU1..CPU10:

String processor_model = /* user input */;
GenericCPU cpu;
switch(processor_model) {
    case "CPU1": cpu = new CPU1; break;
    case "CPU2": cpu = new CPU2; break;
    ...
}
cpu.simulate()

现在,我希望能够做到:能够使用相同挂钩代码修改所有十个CPUx类的行为(假设CPU1..10结构​​为类似,足以允许)。我会用#include语句实现的东西,或者如果部分类允许一些语法将相同的代码拉入几个类(例如,在那个概念性的非工作示例中引发问题"为什么在地球上"),或者我现在使用的脚本实际上复制了10个类的钩子代码。

对于c#来说太脏了吗?

3 个答案:

答案 0 :(得分:2)

我怀疑你正在从C / C ++思维模式而不是C#语言中解决这个问题。在早期的语言中,您可以通过#include以您希望的方式重用源代码,但C#更倾向于重新使用已编译的程序集而不是重新使用原始源代码。

除了从公共基类继承之外,向现有类(AFAIK)添加功能的唯一方法是使用扩展方法:

namespace context1
{
    public class A
    {
        internal String s = "context1";
    }
}

namespace context2
{
    public class A
    {
        internal String s = "context2";
    }
}

namespace common
{
    public static class ExtensionsForA
    {
        public static void f(this context1.A a) { common_f(a.s); }
        public static void f(this context2.A a) { common_f(a.s); }

        private static void common_f(string s) { Console.WriteLine(s); }
    }

    class Program
    {
        static void Main(string[] args)
        {
            context1.A a1 = new context1.A();
            context2.A a2 = new context2.A();
            a1.f();
            a2.f();
            Console.ReadKey();
        }
    }
}

不幸的是,这种方法带来了它自己的头痛,例如确保扩展方法可以访问所有必要的成员,并且必须为每个上下文维护适当版本的f(),所以我不一定推荐它。

也许如果您要向我们提供更具体的问题描述,我们可能会为您提供更优雅的解决方案。

答案 1 :(得分:1)

您可以将公共代码委托给一个类。然后只重复对代表的引用。在你的例子中,这不会给你带来太大的收益,但在任何更复杂的情况下,它都会比较干。您甚至可以在自定义部分中添加一个公共接口,只需转发来自委托的调用。这样,接口和实现很常见,但可以逐个方法地轻松更改为独特的行为。

DRYer但不那么健壮(如果另一个部分扩展基数也不能是部分的):

namespace Common {
    abstract class A {
        private readonly string _s;
        protected A(string s){ _s = s; }
        public void F() { Console.WriteLine(_s); }
    }    
}

namespace Context1 {
    partial class A : Common.A {
        public A () : base ("context1");
    }
}

namespace Context2 {
    partial class A : Common.A {
        public A () : base ("context2");
    }
}

class Program {
    static void Main(string[] args) {
        Context1.A a1 = new Context1.A();
        Context2.A a2 = new Context2.A();
        a1.F();
        a2.F();
        Console.ReadKey();
    }
}

更强大,可以使用已扩展基类的部分:

namespace Common {
    class A : IA {
        private readonly string _s;
        public A(string s){ _s = s; }
        public void F() { Console.WriteLine(_s); }
    }
    interface IA {
        void F();
    }     
}

namespace Context1 {
    partial class A : IA {
        private IA _a = new Common.A("context1");
        public void F(){return _a.F();}
    }
}

namespace Context2 {
    partial class A : IA {
        private IA _a = new Common.A("context2");
        public void F(){return _a.F();}
    }
}

class Program {
    static void Main(string[] args) {
        Context1.A a1 = new Context1.A();
        Context2.A a2 = new Context2.A();
        a1.F();
        a2.F();
        Console.ReadKey();
    }
}

答案 2 :(得分:0)

C#是一种编译语言,因此,程序运行时所有符号名称都消失了;唯一剩下的就是地址和抵消。如果我们通过在第二个分类中的成员s之前添加一些内容,以下列方式稍微修改您的程序;我们看到它的偏移量在第二种情况下会有所不同:

namespace context1 {
    using common; // using as #include
    partial class A {
        String s = "contetx1";
    }
}

namespace context2 {
    using common; // using as #include
    partial class A {
        String a = "blablabla";
        String s = "context2";
    }
}

这就是为什么你的想法不起作用的原因,因为函数public void f() { Console.WriteLine(s); }有时不可能为访问变量s取一个偏移量,而另一个部分类取代另一个偏移量。就编译器而言,即使它们具有相同的名称,您的两个变量s是两个不同的变量,它们之间没有任何共同之处,您也可以给它们两个不同的名称;例如s1s2

如果您不想使用通用界面或复制代码,那么我看到的唯一剩下的解决方案是将这些常见变量及其相关代码分组到另一个对象中。