views:

251

answers:

6

I'd like to have the following setup:

class Descriptor
{
    public string Name { get; private set; }
    public IList<Parameter> Parameters { get; private set; } // Set to ReadOnlyCollection

    private Descrtiptor() { }
    public Descriptor GetByName(string Name) { // Magic here, caching, loading, parsing, etc. }
}

class Parameter
{
    public string Name { get; private set; }
    public string Valuie { get; private set; }
}

The whole structure will be read-only once loaded from an XML file. I'd like to make it so, that only the Descriptor class can instantiate a Parameter.

One way to do this would be to make an IParameter interface and then make Parameter class private in the Descriptor class, but in real-world usage the Parameter will have several properties, and I'd like to avoid redefining them twice.

Is this somehow possible?

+1  A: 

You could use a constructor marked Internal.

That way it's public to classes in the assembly, and private to classes outside of it.

Aequitarum Custos
+10  A: 

Make it a private nested class that implements a particular interface. Then, only the outer class can instantiate it, but anyone can consume it (through the interface). Example:

interface IParameter
{ 
    string Name { get; } 
    string Value { get; }
}

class Descriptor
{
    public string Name { get; private set; }
    public IList<IParameter> Parameters { get; private set; }

    private Descriptor() { }
    public Descriptor GetByName(string Name) { ... }

    class Parameter : IParameter
    {
        public string Name { get; private set; }
        public string Value { get; private set; }
    }
}

If you really must avoid the interface, you can create a public abstract class that has all of the properties but declares a protected constructor. You can then create a private nested class that inherits from the public abstract that can only be created by the outer class and return instances of it as the base type. Example:

public abstract AbstractParameter
{ 
    public string Name { get; protected set; } 
    public string Value { get; protected set; }
}

class Descriptor
{
    public string Name { get; private set; }
    public IList<AbstractParameter> Parameters { get; private set; }

    private Descriptor() { }
    public Descriptor GetByName(string Name) { ... }

    private class NestedParameter : AbstractParameter
    {
        public NestedParameter() { /* whatever goes here */ }
    }
}
LBushkin
This still lets you create another subclass of Parameter and instantiate through that. Make the constructor `protected internal`, perhaps.
Martinho Fernandes
@Martinho: you are incorrect. It's a common misconception that "protected internal" prevents classes from outside the assembly from subclassing. You should think of it as protected OR internal, not protected AND internal - i.e. the class can be derived from any other class inside or outside the assembly, but can only be instantiated internally.
Joe
+4  A: 

LBushkin has the right idea. If you want to avoid having to retype all the properties just right-click the name of the class and choose "Refactor" > "Extract Interface", that should give you an interface that contains all those properties. (This works in VS 2008, I don't know about earlier versions.)

C# generally takes the approach that instead of avoiding redundant code, VS will just help you write it faster.

Nate C-K
A: 

there is another way, check the call stack for the calling type :)

alexm
Runtime checks like this are typically inadvisable - both because they are expensive (due to reflection) and fragile - if new derived classes are added that should also be able to instantiate the type.
LBushkin
They're fragile for other reasons too: method inlining anyone?
Martinho Fernandes
Obfuscators also break this.
Michael Stum
It was a joke guys..
alexm
A: 

If you want only the Descriptor class to instantiate a Parameter, then you can make the Descriptor class a nested class of Parameter. (NOT the other way around) This is counterintuitive as the container or parent class is the nested class.

public class Parameter  
{  
  private Parameter() { }
  public string Name { get; private set; }
  public string Value { get; private set; }
  public static Parameter.Descriptor GetDescriptorByName(string Name)
  {
    return Parameter.Descriptor.GetByName(Name);
  }
  public class Descriptor
  { // Only class with access to private Parameter constructor
    private Descriptor() { // Initialize Parameters }
    public IList<Parameter> Parameters { get; private set; } // Set to ReadOnlyCollection
    public string Name { get; private set; }
    public static Descriptor GetByName(string Name) { // Magic here, caching, loading, parsing, etc. }
  }  
}
DRBlaise
A: 

Mark the class to be "protected" from instantiation (Parameter) with the StrongNameIdentityPermission attribute and the SecurityAction.LinkDemand option:

[StrongNameIdentityPermission(SecurityAction.LinkDemand, PublicKey="...")]
class Parameter
{
    ...
}

You will need to provide the appropriate public key. Because you are demanding a link-time (JIT-time, in fact) check on the Parameterclass, this means that it can only be used from an assembly that is signed with a strong name that uses the private key matching the public key that you supply in the attribute constructor above. Of course, you will need to put the Descriptor class in a separate assembly and give it a strong name accordingly.

I have used this technique in a couple of applications and it worked very well.

Hope this helps.

CesarGon