4

Is it possible to define a compiler constant on a PER-FILE/project-item basis ?

Background: I want to achieve a Database Abstraction Layer (DAL), that separates all read, and write tasks, but retain a DAL that can do both, but without implementing the same methods multiple times (abstract class means there will be 1 instance class for every supported database type).

So I want to separate my DAL like this:

abstract class ReadDAL
abstract class WriteDAL
abstract class ReadWriteDAL (multiple-inheritance from Read&Write-DAL).

Unfortunately, that doesn't work, because C# doesn't support multiple inheritance.

So one way around this problem would be by defining interfaces:

abstract class ReadDAL : IReadDAL
abstract class WriteDAL : IWriteDAL
abstract class ReadWriteDAL : IReadDAL, IWriteDAL

However, if I do this, I'll have to change the interface definition every time I change a method in one of the DALs, and change the methods defined in ReadWriteDAL, and I have to copy-paste somewhere the method implementation, which means there will be a DRY-noncompliance mess.

I figured what I could do was adding the same file a second time as link, and having a define on a per-project-item basis:

#if SOMECONSTANT // true if file is PartialReadDAL.cs
public partial abstract class ReadDAL
#else // false if "file" is link called "PartialReadWriteDAL.cs" symlinking to PartialReadDAL.cs
public partial abstract class ReadWriteDAL
#endif 
and here some implementation. 

But can I somehow define a compiler constant per file ?
Or achieve a similar effect somehow ?

Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
  • Can you implement your methods as extension methods over the interfaces? – Lucas Trzesniewski Dec 09 '16 at 19:30
  • @Lucas Trzesniewski: First, I don't want extension methods. Second probably not, even if I wanted to. – Stefan Steiger Dec 09 '16 at 19:32
  • Idea: generate `ReadWriteDAL` from a T4 template that generates forwarding methods to `ReadDAL` and `WriteDAL` (instances of which can be passed to the constructor, or assuming suitable defaults). In essence, `ReadWriteDAL` contains no interesting code, so it can and probably should be written by a computer. Another idea that doesn't involve code generation: give `ReadWriteDAL` implicit conversion operators to `(I)ReadDAL` and `(I)WriteDAL` that just return appropriate instances. This doesn't substitute quite as nicely as the real thing, but it's close. Both ideas rely on composition. – Jeroen Mostert Dec 09 '16 at 19:56

1 Answers1

0

The symlink route would be very, very confusing. When forced into doing this, I would implement that by prepending some #defines into relevant files as a prebuild step. Then I would #if on presence of these symbols in the code. I wouldn't like this at all though: my guess is that this would not be as transparent as I would like even if I cleared this markers after build's end so it won't get in version control.

Is ReadWriteDAL going to contain some state of it's own, or is it going to be just a dispatcher for method calls into ReadDAL and WriteDAL? If it's just a dispatcher, you might consider to drop actual implementation (ReadWriteDAL) and pass calls to IReadDAL and IWriteDAL as registered in composition root, using dynamic proxy mechanism. I wrote a tool like that for Castle Windsor.

Lukáš Lánský
  • 4,641
  • 3
  • 32
  • 48