2009-11-03 18 views
43

¿Es posible especificar que los miembros de una clase anidada pueden ser accedidos por la clase adjunta, pero no por otras clases?¿Cómo restringir el acceso al miembro de la clase anidado a la clase adjunta?

Aquí es una ilustración del problema (por supuesto, mi código real es un poco más compleja ...):

public class Journal 
{ 
    public class JournalEntry 
    { 
     public JournalEntry(object value) 
     { 
      this.Timestamp = DateTime.Now; 
      this.Value = value; 
     } 

     public DateTime Timestamp { get; private set; } 
     public object Value { get; private set; } 
    } 

    // ... 
} 

me gustaría evitar que el código del cliente desde la creación de instancias de JournalEntry, pero debe haber Journal capaz de crearlos Si hago el constructor público, cualquiera puede crear instancias ... pero si lo hago de forma privada, ¡Journal no podrá!

Tenga en cuenta que la clase JournalEntry debe ser pública, porque quiero poder exponer las entradas existentes al código del cliente.

¡Cualquier sugerencia sería apreciada!


ACTUALIZACIÓN: Gracias a todos por sus comentarios, que finalmente fue para el IJournalEntry interfaz pública, implementada por una clase privada JournalEntry (a pesar del último requisito en mi pregunta ...)

+1

Se podría hacer el constructor (objeto) JournalEntry 'interna'; esto evitaría que otras asambleas instanciaran entradas de diario, pero otras clases en el mismo ensamblado todavía pueden hacerlas; sin embargo, si usted es el autor de la asamblea, esto podría ser suficiente. –

+0

sí, pensé en eso, pero preferiría no poder crear instancias incluso en el mismo ensamblado ... ¡gracias de todos modos! –

Respuesta

33

Si su clase no es demasiado complejo que podría o bien utilizar una interfaz que es públicamente visible y hacer que la clase que implementa real privado o usted podría hacer un constructor protegida para la clase JornalEntry y tiene una clase privada JornalEntryInstance derivada de JornalEntry con un constructor público que en realidad está instanciado por su Journal.

public class Journal 
{ 
    public class JournalEntry 
    { 
     protected JournalEntry(object value) 
     { 
      this.Timestamp = DateTime.Now; 
      this.Value = value; 
     } 

     public DateTime Timestamp { get; private set; } 
     public object Value { get; private set; } 
    } 

    private class JournalEntryInstance: JournalEntry 
    { 
     public JournalEntryInstance(object value): base(value) 
     { } 
    } 
    JournalEntry CreateEntry(object value) 
    { 
     return new JournalEntryInstance(value); 
    } 
} 

Si su clase real es demasiado complejo para realizar una de eso y usted puede conseguir lejos con el constructor no ser completamente invisible, puede hacer que el constructor interna por lo que sólo es visible en la asamblea.

Si eso también es factible siempre se puede hacer que el constructor reflexión privada y el uso llamarlo de su clase de revista:

typeof(object).GetConstructor(new Type[] { }).Invoke(new Object[] { value }); 

Ahora que lo pienso, otra posibilidad sería utilizar un delegado privada en el que contiene la clase que se establece a partir de la clase interna

public class Journal 
{ 
    private static Func<object, JournalEntry> EntryFactory; 
    public class JournalEntry 
    { 
     internal static void Initialize() 
     { 
      Journal.EntryFactory = CreateEntry; 
     } 
     private static JournalEntry CreateEntry(object value) 
     { 
      return new JournalEntry(value); 
     } 
     private JournalEntry(object value) 
     { 
      this.Timestamp = DateTime.Now; 
      this.Value = value; 
     } 

     public DateTime Timestamp { get; private set; } 
     public object Value { get; private set; } 
    } 

    static Journal() 
    { 
     JournalEntry.Initialize(); 
    } 

    static JournalEntry CreateEntry(object value) 
    { 
     return EntryFactory(value); 
    } 
} 

Esto debería darle sus niveles de visibilidad deseadas sin necesidad de recurrir a la reflexión lenta o la introducción de clases/interfaces adicionales

+0

La interfaz es una buena solución, no sé por qué no pensé en eso ... ¡Gracias! –

+3

El primer enfoque tiene una gran desventaja: otras clases también pueden heredarse de JournalEntry y luego llamar al constructor. –

2

En este caso podría ya sea:

  1. Hacer el constructor interna - esto detiene aquellos fuera de esta Asamblea la creación de nuevas instancias o ...
  2. Refactor la clase JournalEntry utilizar una interfaz pública y hacer que la clase real JournalEntry sea privada o interna. La interfaz se puede exponer para colecciones mientras la implementación real está oculta.

I mencionado interno como un modificador válido anterior sin embargo, dependiendo de sus necesidades, privada puede ser la alternativa más adecuada.

Edit: Lo siento, he mencionado el constructor privado pero ya ha tratado este punto en su pregunta. ¡Mis disculpas por no leerlo correctamente!

1

yo haría el constructor JournalEntry interna:

public class Journal 
{ 
    public class JournalEntry 
    { 
     internal JournalEntry(object value) 
     { 
      this.Timestamp = DateTime.Now; 
      this.Value = value; 
     } 

     public DateTime Timestamp { get; private set; } 
     public object Value { get; private set; } 
    } 

    // ... 
} 
15

Marca JournalEntry a tipo anidado privado. Todos los miembros públicos serán visibles solo para el tipo adjunto.

public class Journal 
{ 
    private class JournalEntry 
    { 
    } 
} 

Si usted necesita para hacer JournalEntry objetos disponibles para otras clases, exponerlos a través de una interfaz pública:

public interface IJournalEntry 
{ 
} 

public class Journal 
{ 
    public IEnumerable<IJournalEntry> Entries 
    { 
     get { ... } 
    } 

    private class JournalEntry : IJournalEntry 
    { 
    } 
} 
64

En realidad no es una solución completa y sencilla para este problema que no implique la modificación el código del cliente o la creación de una interfaz.

Esta solución es en realidad más rápida que la solución basada en interfaz para la mayoría de los casos, y más fácil de codificar.

public class Journal 
{ 
    private static Func<object, JournalEntry> _newJournalEntry; 

    public class JournalEntry 
    { 
    static JournalEntry() 
    { 
     _newJournalEntry = value => new JournalEntry(value); 
    } 
    private JournalEntry(object value) 
    { 
     ... 
+3

Un enfoque agradable y original ... Tengo que recordarlo. Gracias ! –

+0

¡Esto es muy bonito, una pieza preciosa de código! –

+2

Spiffy. La respuesta aceptada tiene la misma idea general, pero viene al pie de muchas otras alternativas, así que ¡mira aquí! – aggieNick02

10

Un enfoque más sencillo es usar un constructor de internal, pero hacer que la persona que llama demostrar lo que son mediante el suministro de una referencia que solamente la persona que llama legítima podía saber (que no es necesario preocuparse por no -publicación pública, porque si la persona que llama tiene acceso a la reflexión no pública, entonces ya hemos perdido la batalla; pueden acceder directamente al constructor private); por ejemplo:

class Outer { 
    // don't pass this reference outside of Outer 
    private static readonly object token = new object(); 

    public sealed class Inner { 
     // .ctor demands proof of who the caller is 
     internal Inner(object token) { 
      if (token != Outer.token) { 
       throw new InvalidOperationException(
        "Seriously, don't do that! Or I'll tell!"); 
      } 
      // ... 
     } 
    } 

    // the outer-class is allowed to create instances... 
    private static Inner Create() { 
     return new Inner(token); 
    } 
} 
+0

¡Buena idea! Voy a tener este truco en mente, por si acaso ... –

0

Para la clase anidada genérica =)

sé que esto es una cuestión de edad y tiene ya una respuesta aceptada, sin embargo, para aquellos nadadores de Google que puede tener un escenario similar a la mía esta respuesta puede proporcionar algo de ayuda.

Me encontré con esta pregunta porque necesitaba implementar la misma función que el OP. Para mi primer escenario, las respuestas this y this funcionaron bien. Sin embargo, también necesitaba exponer una clase genérica anidada. El problema es que no puede exponer un campo de tipo delegado (el campo de fábrica) con parámetros genéricos abiertos sin hacer genérica su propia clase, pero obviamente esto no es lo que queremos, así que aquí está mi solución para tal escenario:

public class Foo 
{ 
    private static readonly Dictionary<Type, dynamic> _factories = new Dictionary<Type, dynamic>(); 

    private static void AddFactory<T>(Func<Boo<T>> factory) 
     => _factories[typeof(T)] = factory; 

    public void TestMeDude<T>() 
    { 
     if (!_factories.TryGetValue(typeof(T), out var factory)) 
     { 
      Console.WriteLine("Creating factory"); 
      RuntimeHelpers.RunClassConstructor(typeof(Boo<T>).TypeHandle); 
      factory = _factories[typeof(T)]; 
     } 
     else 
     { 
      Console.WriteLine("Factory previously created"); 
     } 

     var boo = (Boo<T>)factory(); 
     boo.ToBeSure(); 
    } 

    public class Boo<T> 
    { 
     static Boo() => AddFactory(() => new Boo<T>()); 

     private Boo() { } 

     public void ToBeSure() => Console.WriteLine(typeof(T).Name); 
    } 
} 

tenemos Boo como nuestra clase anidada interna con un constructor privado y Mantenemos en nuestra clase padre de un diccionario con estas fábricas genéricos aprovechando dinámica. Entonces, cada vez que se llama a TestMeDude, Foo busca si la fábrica para T ya se ha creado, si no lo crea llamando al constructor estático de la clase anidada.

Pruebas:

private static void Main() 
{ 
    var foo = new Foo(); 

    foo.TestMeDude<string>(); 
    foo.TestMeDude<int>(); 
    foo.TestMeDude<Foo>(); 

    foo.TestMeDude<string>(); 

    Console.ReadLine(); 
} 

La salida es:

enter image description here

Cuestiones relacionadas