简体   繁体   中英

c#: re-use partial class definition in two different contexts

I am trying to re-use common partial class definition to define several different classes. Here's the conceptual (non-working) code that illustrates the desired result:

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

The code in context1 and context2 is auto-generated. I want to use same piece of manually written code (namespace 'common' above) in auto-generated classes. Currently I replicate the common chunk with the auto-generator script, but wondering is there's a more elegant way to do this in c#?

To clarify - above code is just an illustration, I understand why it doesn't work (can't split partial class definition across namespaces). Also, while in the example above I could just have a base common class, in actuality that really needs to be same class, which I achieve with partial definition.

--- added later ---

Ok, after reading the answers I realize that the answer to my question is "No", and I appreciate you guys confirming my fear that there's no easy way out using partial classes. Let me try to give more details on how I came to such question. Maybe you'll point me to some easy and elegant solution. Imagine there's a huge class describing some hardware model together with methods to simulate it, and it is generated automatically from some hardware description. Something like:

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

Suppose that without anything else this simulates some CPU (or whatever). Partial hook methods and calls get optimized away, everyone happy. Now, I want to be able to easily define the hooks outside the auto-generated file, and I need the hooks to be able to access all their respective classes fields and methods. Defining a special interface for the data the hook needs every time I want to use one of the hooks is out of the question. Suppose I want to do some special stuff at the beginning and at the end of CPU1.alu758.get_data(), it's super easy to do with partial classes and methods:

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 */ }
    }
}

While there's just one CPU1 class this works perfectly. But let's say there are 10 auto-generated CPU classes 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()

Now, here's what I want to be able to do: be able to modify the behavior of all ten CPUx classes with same hooks code (just assume CPU1..10 structure is similar enough to allow that). Something I would achieve with #include statement, or if partial classes allowed some syntax to pull same code into several classes (like eg in that conceptual non-working example which begs the question "why on earth"), or what I do now with a script that literally replicates hook code across 10 classes.

Is that too dirty for c#?

I suspect you are approaching this problem from more of a C/C++ mindset than a C# one. In the earlier languages, you could re-use source code via #include in the way you want, but C# is much more geared toward the idea of re-using compiled assemblies rather than re-using raw source.

Aside from inheriting from a common base class, the only way to add functionality to an existing class (AFAIK) would be to use extension methods:

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

Unfortunately, this approach carries its own headaches with it, such as ensuring the extension methods have access to all the necessary members and having to maintain an appropriate version of f() for every context, so I don't necessarily recommend it.

Perhaps if you were to give us a more specific description of your problem, we might be able to offer you a more elegant solution.

You could delegate the common code to a class. Only the reference to the delegate will be repeated then. This wouldn't buy you much in your example, but in anything more complex it would be rather DRY. You could even add a common interface in your custom partials that just forward calls from the delegate. That way the interface and implementation are common, but could be easily changed to unique behavior on a method by method basis.

DRYer but less robust (can't be partial if another partial extends base as well):

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

More robust and will work with partial that already extends base class:

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

C# is a compiled language and therefore, all the symbolic names are gone when the program is running; the only things remaining are addresses and offsets. If we modify slightly your program in the following way by adding something before the member s in the second partial class; we see that its offset will be different in the second case:

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";
    }
}

This is why your idea doesn't work because it's impossible for the function public void f() { Console.WriteLine(s); } public void f() { Console.WriteLine(s); } to sometime take an offset for accessing the variable s but for another partial class to take another offset instead. As far as the compiler is concerned, even if they have the same name, your two variables s are two different variables with nothing in common between them and you could as well had given them two different names; for example s1 and s2 instead.

If you don't want to use a common interface or to duplicate the code, that only remaining solution that I see would be to group these common variables along with their associated code into another object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM