c# - functions - ¿Cómo restringir el acceso al miembro de la clase anidada a la clase adjunta?
protection level c# (7)
En este caso usted podría:
- Hacer que el constructor sea interno: esto detiene a los que están fuera de este ensamblaje creando nuevas instancias o ...
- Refactorice la clase
JournalEntry
para utilizar una interfaz pública y haga que la claseJournalEntry
real sea privada o interna. La interfaz puede ser expuesta para colecciones mientras la implementación real está oculta.
Mencioné el interno como un modificador válido más arriba, sin embargo, dependiendo de sus requisitos, la alternativa privada podría ser la más adecuada.
Edición: Lo siento, mencioné a un constructor privado, pero ya ha tratado este punto en su pregunta. Mis disculpas por no leerlo correctamente!
¿Es posible especificar que se puede acceder a los miembros de una clase anidada por la clase adjunta, pero no a otras clases?
Aquí hay una ilustración del problema (por supuesto, mi código real es un poco más complejo ...):
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 cree instancias de JournalEntry
, pero Journal
debe poder crearlas. Si hago público al constructor, cualquiera puede crear instancias ... ¡pero si lo hago privado, 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 su aporte, finalmente IJournalEntry
interfaz pública IJournalEntry
, implementada por una clase privada de JournalEntry
(a pesar del último requisito en mi pregunta ...)
En realidad, hay una solución completa y simple para este problema que no implica modificar el código del cliente o crear 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)
{
...
Haga de JournalEntry
un tipo anidado privado . Cualquier miembro público será visible solo para el tipo adjunto.
public class Journal
{
private class JournalEntry
{
}
}
Si necesita que los objetos JournalEntry
estén disponibles para otras clases, expóngalos a través de una interfaz pública:
public interface IJournalEntry
{
}
public class Journal
{
public IEnumerable<IJournalEntry> Entries
{
get { ... }
}
private class JournalEntry : IJournalEntry
{
}
}
La solución que Grizzly sugirió hace que sea un poco difícil crear la clase anidada en otro lugar, pero no imposible, como Tim Pohlmann escribió que alguien todavía puede heredarla y usar el ctor heredado de la clase.
Estoy aprovechando el hecho de que la clase anidada puede acceder a las propiedades privadas del contenedor, por lo que el contenedor pregunta muy bien y la clase anidada da acceso al ctor.
public class AllowedToEmailFunc
{
private static Func<long, EmailPermit> CreatePermit;
public class EmailPermit
{
public static void AllowIssuingPermits()
{
IssuegPermit = (long userId) =>
{
return new EmailPermit(userId);
};
}
public readonly long UserId;
private EmailPermit(long userId)
{
UserId = userId;
}
}
static AllowedToEmailFunc()
{
EmailPermit.AllowIssuingPermits();
}
public static bool AllowedToEmail(UserAndConf user)
{
var canEmail = true; /// code checking if we can email the user
if (canEmail)
{
return IssuegPermit(user.UserId);
}
else
{
return null
}
}
}
Esta solución no es algo que haría en un día regular en el trabajo, no porque conlleve problemas en otros lugares, sino porque es poco convencional (nunca lo había visto antes), por lo que podría causarle dolor a otros desarrolladores.
Si su clase no es demasiado compleja, puede usar una interfaz que sea públicamente visible y hacer que la clase de implementación sea privada o hacer un constructor protegido para la clase JornalEntry
y tener una clase privada JornalEntryInstance
derivada de JornalEntry
con un constructor público que es En realidad 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 compleja para hacer algo de eso y puede salirse con la suya, ya que el constructor no es completamente invisible, puede hacer que el constructor sea interno para que solo sea visible en el ensamblaje.
Si eso tampoco es factible, siempre puedes hacer que el constructor sea privado y usar la reflexión para llamarlo desde tu clase de diario:
typeof(object).GetConstructor(new Type[] { }).Invoke(new Object[] { value });
Ahora que lo pienso, otra posibilidad sería usar un delegado privado en la clase contenedora que se establece desde 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 deseados sin necesidad de recurrir a una reflexión lenta o introducir clases / interfaces adicionales
Un enfoque más simple es usar un constructor internal
, pero hacer que la persona que llama compruebe quiénes son al proporcionar una referencia que solo la persona legítima podría conocer (no debemos preocuparnos por la reflexión no pública, porque si la persona que llama tiene acceso a la reflexión no pública, entonces ya perdimos la lucha (pueden acceder directamente a un 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);
}
}
Para la clase anidada genérica =)
Sé que esta es una pregunta antigua y ya tiene una respuesta aceptada, sin embargo, para aquellos nadadores de Google que puedan tener un escenario similar al mío, esta respuesta puede proporcionar ayuda.
Encontré esta pregunta porque necesitaba implementar la misma función que el OP. Para mi primer escenario, this y this respuestas 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 su propia clase genérica, pero obviamente esto no es lo que queremos, por lo 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 a Boo como nuestra clase interna anidada con un constructor privado y mantenemos en nuestra clase primaria un diccionario con estas fábricas genéricas aprovechando la dinámica . Por lo tanto, cada vez que se llama TestMeDude , Foo busca si ya se ha creado la fábrica para T, si no lo hace, llama 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: