可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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:
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 */ }
}
}
回答2:
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.
回答3:
You could use a constructor marked Internal.
That way it's public to classes in the assembly, and private to classes outside of it.
回答4:
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 Parameter
class, 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.
回答5:
There is another way: check the call stack for the calling type.
回答6:
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. }
}
}