Making constructor visible only to a certain class

P

Peter Gummer

This is a design question.

I have a project containing a dozen or so classes with protected
internal constructors.

My reason for having protected internal constructors is to prevent
classes in other assemblies from instantiating them. There is a factory
class that instantiates them. Only the factory should do so, because
some of these classes have subclasses that will be created instead. The
decision of which class to instantiate belongs to the factory.

I've got this working just fine, except one detail.

Because the constructors are protected internal, it's possible for
classes inside the project to by-pass the factory. It's not too hard to
ensure this doesn't happen, by checking manually within the project;
but it would be easier and safer if the compiler could catch this
mistake.

Ideally, I'd like the constructors to have a more restricted visibility
than "internal". I'd like to specify that only the factory can see
them. I.e., instead of this:

protected internal Foo() {}

I'd like to be be able to write:

protected visibleto(Factory) Foo() {}

Unfortunately, C# has extremely coarse-grained visiblity modifiers.

I can think of a couple of work-arounds:

Work-around 1. Declare the constructors protected and let the factory
use reflection to instantiate them.

Work-around 2. Declare a nested protected class within the factory for
each of the classes it instantiates. The nested classes would have
public constructors. Then only the factory, or its descendants, could
instantiate the classes. Exactly what I want!

Both solutions would be worse than the original problem. Reflection
(Work-around 1) kills type-safety, so I avoid it unless I absolutely
need it; besides, it's slow. Work-around 2, on the other hand, would be
a bad case of code-bloat.

Is there a decent solution to this in C#?

-- Peter Gummer
 
P

Peter Rilling

It would be nice if there was a "friend" concept in c#. If I may ask, why
can you not trust the code within your own assembly?
 
P

Peter Gummer

Peter said:
It would be nice if there was a "friend" concept in c#. If I may
ask, why can you not trust the code within your own assembly?

Well, I can trust it only insofar as I am infallible. I am but human,
so I'm sure to make mistakes :)

It would be very easy to forget to use the factory. New developers will
also have to learn that my design involves calling the factory. I have
documented the design, but if someone fails to read the documentation
then they will probably just write "new Foo()" instead of
"Factory.NewFoo".

My preference is for Eiffel, not C++, so I was wishing for "selective
export" rather than "friend". But you get the idea.

-- Peter Gummer
 
J

Joanna Carter [TeamB]

"Peter Gummer" <[email protected]> a écrit dans le
message de news: (e-mail address removed)...

| Because the constructors are protected internal, it's possible for
| classes inside the project to by-pass the factory. It's not too hard to
| ensure this doesn't happen, by checking manually within the project;
| but it would be easier and safer if the compiler could catch this
| mistake.

How about using the InternalsVisibleTo attribute to allow one assembly
access to something internal from another assembly.

This means that you could declare your classes with internal constructors in
one assembly and the factory in a "friend" assembly. As long as the factory
is the only class in the "friend" assembly, then it will be the only class
that can instantiate your types.

Joanna
 
A

Andreas Mueller

Joanna said:
"Peter Gummer" <[email protected]> a écrit dans le
message de news: (e-mail address removed)...

| Because the constructors are protected internal, it's possible for
| classes inside the project to by-pass the factory. It's not too hard to
| ensure this doesn't happen, by checking manually within the project;
| but it would be easier and safer if the compiler could catch this
| mistake.

How about using the InternalsVisibleTo attribute to allow one assembly
access to something internal from another assembly.

This means that you could declare your classes with internal constructors in
one assembly and the factory in a "friend" assembly. As long as the factory
is the only class in the "friend" assembly, then it will be the only class
that can instantiate your types.

Joanna

That's a cool one :). I wish I knew that a year ago.

But this applies just for the complete type, not one of its fields. I
think the OPs intend was to declare just the ctors private.

Here's a way to make the just ctor private:

public class AA
{
private AA() { }
}

public static class AAFactory
{
public static AA CreateAA()
{
return (AA)Activator.CreateInstance(typeof(AA), true);
}
public static T Create<T>()
{
return (T)Activator.CreateInstance(typeof(T), true);
}
}

AA a1 = AAFactory.Create<AA>();
AA a2 = AAFactory.CreateAA();


However, if it is possible for the factory just to return a base class
or interface, Joannas aproach is the cleanest.

HTH,
Andy
 
N

Nick Hounsome

Peter Gummer said:
This is a design question.

I have a project containing a dozen or so classes with protected
internal constructors.

My reason for having protected internal constructors is to prevent
classes in other assemblies from instantiating them. There is a factory
class that instantiates them. Only the factory should do so, because
some of these classes have subclasses that will be created instead. The
decision of which class to instantiate belongs to the factory.

"protected internal" allows calling by the ctors of derived classes which
doesn't sound like what you want.
 
A

Abubakar

Why not just make the constructors private and use the factory thing
everywhere?

One more thing, have you checked FxCop? I think you can make a rule in it
and it'll do the enforcement of not using "new" that you want in your
current approach. Just an idea.

Regards.

-Ab.
 
P

Peter Gummer

Nick said:
"protected internal" allows calling by the ctors of derived classes
which doesn't sound like what you want.

That's exactly what I want.

I'm not trying to restrict the capabilities of subclasses. I'm trying
to restrict the capabilities of clients; i.e., I don't want other
objects to be able to instantiate the product classes. Only the factory
should instantiate them.

So no: I definitely do not want "internal"; "protected internal" is
closer to what I want. My problem is that "internal" is too crude a
mechanism; "protected" is just fine.

-- Peter Gummer
 
P

Peter Gummer

Abubakar said:
Why not just make the constructors private and use the factory thing
everywhere?

If I made the constructors "private" (or "protected"), the factory
would not have access to the constructors, so it would be unable to
instantiate the product classes -- unless, of course, I used one of the
two work-arounds that I mentioned in my original post (i.e. reflection,
for which Andreas Mueller gave sample code, or writing a protected
subclass for each product class nested within the factory) -- both of
which cures I feel are worse than the original problem.

Am I missing something in your "private" suggestion?

Thanks for the FxCop idea. We might end up doing that. It's a pain,
though, having to use a tool like FxCop to overcome the deficiencies of
the language.

-- Peter Gummer
 
P

Peter Gummer

Joanna said:
How about using the InternalsVisibleTo attribute to allow one
assembly access to something internal from another assembly.

This means that you could declare your classes with internal
constructors in one assembly and the factory in a "friend" assembly.
As long as the factory is the only class in the "friend" assembly,
then it will be the only class that can instantiate your types.

Something like that might work. I didn't know about InternalsVisibleTo.

I wouldn't make the constructors "internal", though. That's exactly
what I'm trying to prevent: products within the assembly creating each
other directly. If I simply made the constructors "protected", then
your idea should work.

The idea of adding another project, containing nothing but the factory,
doesn't really appeal to me. I actually have several factories (a fact
I didn't mention earlier), so I'd finish up with several teensy-weensy
factory projects. My initial feeling is that this is another cure worse
than the original problem; but I'll think about it.

Thanks for the suggestion.

-- Peter Gummer
 
A

Abubakar

Am I missing something in your "private" suggestion?

maybe yes. Check the following code:

// <code> written in c# 2k5 rtm
namespace factorytest
{
class Program
{
static void Main(string[] args)
{
//MyFactory mf = new MyFactory(); //causes error, ctor not accessible
anywhere

//but possible following way:
MyFactory mf = MyFactory.CreateInstance();
mf.proc1();
}
}

class MyFactory
{
//ctor: cant be done "new" outside this class
private MyFactory()
{
}
public static MyFactory CreateInstance()
{
return new MyFactory(); //"new" possible inside
}

public void proc1() { Console.WriteLine("hi"); }

}
}//end namespace

// </code>

the above code demos what I was trying to say. Does this work for you?

btw, as far as reflection is concerned, it depends on how frequently that
part of code be executing because IMO reflection may slow things down.

-Ab.
 
N

Nick Hounsome

Peter Gummer said:
That's exactly what I want.

I'm not trying to restrict the capabilities of subclasses. I'm trying
to restrict the capabilities of clients; i.e., I don't want other
objects to be able to instantiate the product classes. Only the factory
should instantiate them.

But as a client I can simply create a subclass of your class and instantiate
it and there is nothing you can do to stop me.

class ClientClass: YourClass
{
public ClientClass() : base(...whatever..) // ok because protected
{
}
}

YourClass c = new ClientClass(); // ok
 
T

tdavisjr

Andreas said:
That's a cool one :). I wish I knew that a year ago.

I think this is an added attribute to the .NET FX 2.0. So, if thats
true we couldn't then its new to everyone.
 
P

Peter Gummer

Nick said:
But as a client I can simply create a subclass of your class and
instantiate it and there is nothing you can do to stop me.

Sure. That was one of the work-arounds that I presented in the original
post: make the constructor "protected", then let the factory sub-class
each product class in the manner you showed.

If someone wants to go to all that trouble in some client class other
than the factory, then fine. They must have a good reason for doing so,
if they want to go to all that trouble.

I'm looking for a speed bump, to prevent people from accidentally doing
the obvious (but wrong) thing. I'm not looking for up a concrete
road-block: my colleagues are not malicious joy-riders who are going to
deliberate do dangerous things.

-- Peter Gummer
 
P

Peter Gummer

Abubakar said:
btw, as far as reflection is concerned, it depends on how frequently
that part of code be executing because IMO reflection may slow things
down.

My main objection to reflection is not speed -- although that is likely
to be an important secondary issue in this case -- but how it subverts
the compiler. Type-safety goes out the window.

I use reflection as a tool of last resort.

-- Peter Gummer
 
P

Peter Gummer

Abubakar said:
the above code demos what I was trying to say. Does this work for you?

Thanks, but no.

Your sample code shows a factory with no products! Here's what I have:

public class Factory
{
public Product1 NewProduct1
{
get { return new Product1(); }
}

public Product2 NewProduct2
{
get { return new Product2(); }
}
}

public class Product1
{
protected internal Product1() {}

public void DoThis(Factory factory)
{
factory.NewProduct2.DoThat();
}
}

public class Product2
{
protected internal Product2() {}

public void DoThat()
{
new Product1().DoThis();
}
}

The implementation of DoThat() is bad. It should be:

public void DoThat(Factory factory)
{
factory.NewProduct1.DoThis();
}

Ignore the mutual recursion between DoThis() and DoThat(), please; this
is just sample code ;-)

-- Peter Gummer
 
Y

Yury

Work-around 3. Using prototyping. Not perfect, but still a solution.

namespace FactoryTest
{
public abstract class AbstractFactory
{
public abstract ProductA NewProductA();
public abstract ProductB NewProductB();
}

public class ProductA: ICloneable
{
internal static void ActivateFactory()
{
ConcreteFactory.ProductAPrototype = new ProductA();
}

protected ProductA() {}

public object Clone(){ return MemberwiseClone(); }
}

public class ProductB: ICloneable
{
protected ProductB() {}

public object Clone() { return MemberwiseClone(); }

internal static void ActivateFactory() {
ConcreteFactory.ProductBPrototype = new ProductB();
}
}

public class ConcreteFactory: AbstractFactory
{
public ConcreteFactory() {
ProductA.ActivateFactory();
ProductB.ActivateFactory();
}

static ProductA productAPrototype;
static ProductB productBPrototype;

static public ProductA ProductAPrototype {
set { productAPrototype = value; }
}

static public ProductB ProductBPrototype {
set { productBPrototype = value; }
}

public override ProductA NewProductA() {
return (ProductA)productAPrototype.Clone();
}

public override ProductB NewProductB() {
return (ProductB)productBPrototype.Clone();
}
}

class Program
{
static void Main(string[] args) {

AbstractFactory factory = new ConcreteFactory();
ProductA a = factory.NewProductA();
ProductB b = factory.NewProductB();
}
}
}
 
P

Peter Gummer

Yury said:
Work-around 3. Using prototyping. Not perfect, but still a solution.

Hey, now that's good! It's a bit convoluted, but not too bad. It's an
improvement on what I have.

Plus this is my first ever opportunity to call MemberwiseClone :)

Thanks, Yuri. Great solution!

-- Peter Gummer
 
N

Nick Hounsome

Peter Gummer said:
Sure. That was one of the work-arounds that I presented in the original
post: make the constructor "protected", then let the factory sub-class
each product class in the manner you showed.

It's not a work around - it's what you say you've done - "internal
protected" means "can be accessed by classes in the same assembly OR derived
classes anywhere"

[Maybe you understand this but if you do you haven't been very clear]
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top