【问题标题】:c#: re-use partial class definition in two different contextsc#:在两个不同的上下文中重用部分类定义
【发布时间】:2014-10-05 00:16:23
【问题描述】:

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

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 中的代码是自动生成的。我想在自动生成的类中使用相同的手动编写代码(上面的命名空间'common')。目前我使用自动生成器脚本复制公共块,但想知道在 c# 中有更优雅的方法吗?

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

--- 稍后添加 ---

好的,在阅读了答案后,我意识到我的问题的答案是“否”,感谢你们确认我担心使用部分类没有简单的出路。让我尝试详细说明我是如何提出这个问题的。也许你会告诉我一些简单而优雅的解决方案。想象有一个巨大的类描述了一些硬件模型以及模拟它的方法,它是从一些硬件描述中自动生成的。比如:

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

现在,这就是我想要做的:能够使用 same 钩子代码修改所有十个 CPUx 类的行为(假设 CPU1..10 结构足够相似以允许那)。我会用#include 语句实现的东西,或者如果部分类允许某些语法将相同的代码提取到几个类中(例如在那个概念性的非工作示例中,它提出了“为什么在地球上”的问题),或者我现在用一个从字面上复制 10 个类的钩子代码的脚本。

这对 c# 来说太脏了吗?

【问题讨论】:

  • 简短回答:不。长答案:为什么在地球上?
  • 好的,在下面写完之后,刚刚看到你的更新。即使在不同的命名空间中,它们也不会是同一个类。这就是命名空间的全部意义所在。老实说,我以为我理解你的问题,但现在,不是那么多。

标签: c# namespaces partial-classes


【解决方案1】:

我怀疑您是从更多的 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() 版本,所以我不一定推荐它.

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

【讨论】:

  • 感谢您的回答。但是在我的案例中使用部分类的想法(部分)是为了避免显式定义具有所有必要成员的列表/接口。让我编辑我的问题,为“为什么”部分添加一些描述。
【解决方案2】:

您可以将公共代码委托给一个类。然后只会重复对委托的引用。在您的示例中,这不会给您带来太多好处,但是在更复杂的情况下,它会相当干燥。您甚至可以在自定义部分中添加一个通用接口,该接口仅转发来自委托的调用。这样,接口和实现是通用的,但可以很容易地在一个方法一个方法的基础上更改为独特的行为。

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

【讨论】:

  • 我很感激为答案所花费的时间和精力,并将 +1,但这绝不是一个优雅的解决方案。太复杂了。
  • @glexey 第一个解决方案比您的示例代码更简单,第二个解决方案只是它在 C# 和 Java 中的实现方式。你的进一步解释什么也没做,只会让我更加困惑。没有使用“部分”是简单的。 partial 通常只用于扩展自动生成的代码。如果您出于其他原因需要它,您的代码需要认真重构。老实说,我不知道您如何比基类更简单。那让你不要重复任何事情。
  • “partial 通常只用于扩展自动生成的代码”——这正是我在这里所做的。
  • @glexey 那么在 C# 中我给了你实现它的方法,必须添加只转发通用代码的接口方法并不复杂。无论如何,我认为您可以使用 T4 模板执行与 C#include 相同的操作。这很可能是如何创建自动生成的代码。这对我来说没有意义,而且根本不是惯用的。
【解决方案3】:

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

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

【讨论】:

    猜你喜欢
    • 2010-09-13
    • 2012-07-26
    • 1970-01-01
    • 2013-11-25
    • 1970-01-01
    • 2021-08-10
    • 1970-01-01
    • 1970-01-01
    • 2012-12-23
    相关资源
    最近更新 更多