Внутренний член в интерфейсе
У меня есть список объектов, реализующих интерфейс, и список этого интерфейса:
public interface IAM
{
int ID { get; set; }
void Save();
}
public class concreteIAM : IAM
{
public int ID { get; set; }
internal void Save(){
//save the object
}
//other staff for this particular class
}
public class MyList : List<IAM>
{
public void Save()
{
foreach (IAM iam in this)
{
iam.Save();
}
}
//other staff for this particular class
}
Предыдущий код не компилируется, потому что компилятор требует, чтобы все члены интерфейса были общедоступными.
internal void Save(){
Но я не хочу позволять извне моей DLL сохранять ConcreteIAM
, он должен быть сохранен только через MyList
,
Есть ли способ сделать это?
Обновление № 1: Привет всем, спасибо за ответы, но ни один из них не является тем, что мне нужно:
Интерфейс должен быть общедоступным, потому что это подпись, которую будет использовать клиент извне dll, наряду с ID
и другие свойства, которые я не удосужился написать в примере, чтобы он был простым.
Эндрю, я не думаю, что решение состоит в том, чтобы создать фабрику для создания другого объекта, который будет содержать IAM
Участники + Сохранить. Я все еще думаю... Есть другие идеи?
13 ответов
Я думаю, вы не понимаете, для чего нужен интерфейс. Интерфейс - это контракт. Он указывает, что объект ведет себя определенным образом. Если объект реализует интерфейс, это означает, что вы можете положиться на него, что в нем реализованы все методы интерфейса.
Теперь рассмотрим, что произойдет, если бы был такой интерфейс, о котором вы просите - public, но с одним внутренним членом. Что бы это значило? Внешний объект может реализовывать только открытые методы, но не внутренний. Когда вы получите такой внешний объект, вы сможете вызывать только открытые методы, но не внутренние, потому что объект не может его реализовать. Другими словами - контракт не будет выполнен. Не все методы будут реализованы.
Я думаю, что решение в этом случае состоит в том, чтобы разделить ваш интерфейс на две части. Один интерфейс будет общедоступным, и это будут реализованы ваши внешние объекты. Другой интерфейс будет внутренним и будет содержать ваш Save()
и другие внутренние методы. Возможно, этот второй интерфейс может даже наследоваться от первого. Ваши собственные внутренние объекты будут реализовывать оба интерфейса. Таким образом, вы можете даже различать внешние объекты (те, которые не имеют внутреннего интерфейса) и внутренние объекты.
Создайте другой внутренний интерфейс и используйте явную реализацию метода.
internal interface InternalIAM
{
void Save();
}
public class concreteIAM : InternalIAM
{
void InternalIAM.Save()
{
}
}
Я не думаю, что вы должны использовать интерфейс здесь, может быть, вы должны использовать абстрактную базу что-то вроде:
public abstract class AM
{
public int ID { get; set; }
internal abstract void Save();
}
public class concreteIAM : AM
{
internal override void Save()
{
//Do some save stuff
}
}
Все еще позволит вам сделать это:
public class AMList : List<AM>
{
public void SaveItems()
{
foreach (var item in this)
{
item.Save();
}
}
}
Я думаю, что лучше всего было бы разделить внутренних и открытых членов на два отдельных интерфейса. Если вы наследуете интерфейсы, вы все равно можете объявить элементы публично, но видимость внутренних членов будет продиктована видимостью самого интерфейса.
using System;
public interface IPublic
{
void Public();
}
internal interface IInternal : IPublic
{
void Internal();
}
public class Concrete : IInternal
{
public void Internal() { }
public void Public() { }
}
Это именно то, о чем я говорю в своей статье " Друзья и члены внутреннего интерфейса" без затрат на кодирование интерфейсов.
Суть из статьи
public class Internal
{
internal Internal() { }
}
public interface IAM
{
int ID { get; set; }
void Save(Internal access);
}
Отныне одна только ваша сборка может называть Save()
метод, так как экземпляр Internal
класс может быть создан только вашей сборкой.
Члены интерфейса должны быть открытыми... что-нибудь еще, и вы должны подумать, если вам нужно что-то еще. В этом случае вы
- хотите сохранить, чтобы быть членом интерфейса
- хотите, чтобы сохранение было разрешено только через другой класс с именем MyList, который находится в той же сборке
- запретить сохранение от внешнего вызова (из других классов вне родительской сборки)
Не обращая внимания на дизайн, вы можете сделать это с помощью явной реализации интерфейса.
internal interface IPersist
{
void Save();
}
public class Concrete : IPersist
{
void IPersist.Save()
{
Console.WriteLine("Yeah!");
}
}
// Mylist.cs in the same assembly can still call save like
public void SaveItems()
{
foreach (IPersist item in this)
{
item.Save();
}
}
IPersist является внутренним, недоступным вне родительской сборки, и поскольку он явно реализован, его нельзя вызвать без ссылки на IPersist.
new Concrete().Save(); // doesn't compile. 'Concrete' does not contain a definition for 'Save'
Обновление Из вашего последнего ответа у нас есть больше ограничений. Интерфейс должен быть общедоступным, он должен содержать метод Save, который не должен быть общедоступным. Я бы сказал, вернитесь к чертежной доске... что-то не так. ИМХО Вы не можете сделать это с одним интерфейсом. Как насчет разделения на 2 интерфейса: публичный и внутренний?
Если вы не хотите, чтобы внешние абоненты могли вызывать ваш метод Save(), почему бы не сделать весь конкретный класс IAMIAM внутренним?
Или, если вам нужен класс public, но не интерфейс, сделайте весь интерфейс внутренним. Я думаю, что внутренний интерфейс может быть добавлен в публичный класс (но я не пробовал это...)
Почему вы не используете внутренние классы для контроля доступности ваших методов?
Пример:
Ваша основная сборка
public abstract class Item
{
public int ID { get; set; }
protected abstract void Save();
public class ItemCollection : List<Item>
{
public void Save()
{
foreach (Item item in this) item.Save();
}
}
}
Ваша вторичная сборка
public sealed class NiceItem : Item
{
protected override void Save()
{
// do something
}
}
Этот шаблон все еще позволит вам реализовать Save()
метод в других сборках, но только ItemCollection
который является внутренним классом Item
могу это назвать. Гениально, не правда ли?
Возможно, вы хотите разделить сохранение ваших элементов на другой набор классов, которые являются внутренними для вашей сборки:
internal interface IAMSaver { void Save(IAM item); }
internal class AMSaverFactory {
IAMSaver GetSaver(Type itemType) { ... }
}
public class MyList : List<IAM>
{
public void Save()
{
foreach (IAM itemin this)
{
IAMSaver saver = SaverFactory.GetSaver(item.GetType());
saver.Save(item)
}
}
}
Перейти с двумя интерфейсами:
public interface IAM
{
int ID { get; set; }
}
internal interface IAMSavable
{
void Save();
}
public class concreteIAM : IAM, IAMSavable
{
public int ID{get;set;}
public void IAMSavable.Save(){
//save the object
}
//other staff for this particular class
}
public class MyList : List<IAM>
{
public void Save()
{
foreach (IAM iam in this)
{
((IAMSavable)iam).Save();
}
}
//other staff for this particular class
}
Реализация Save() должна быть явной, чтобы клиенты не вызывали ее.
Как насчет этого?
public interface IAM
{
int ID { get; set; }
internal void Save();
}
public class concreteIAM : IAM
{
public int ID { get; set; }
void IAMSavable.Save() {
//save the object
}
}
Вы можете объявить член интерфейса как внутренний, однако, когда вы реализуете его в классе, у вас есть только два варианта: либо вы объявляете его общедоступным, либо как явную реализацию интерфейса. Реализуя его как явную реализацию интерфейса, как это сделано в примере кода, можно получить только путем приведения
concreteIAM
к
IAM
, таким образом
Save()
становится эффективно
internal
.
Это концептуально очень похоже на решение, предложенное в этом ответе , однако нет необходимости объявлять второй интерфейс.
У меня была похожая ситуация, и я подумал, что это может помочь. (не уверен, если это не нужно в данный момент или нет)
Для меня это законный случай: предположим, что есть интерфейс, который имеет некоторый API, который является внутренним для DLL, и к некоторому API можно получить доступ из-за пределов DLL. Поскольку интерфейс является абстрактным классом, вместо определения в качестве интерфейса вы можете определить его как абстрактный класс.
using System.Collections.Generic;
public abstract class IAM
{
int ID { get; set; }
internal abstract void Save();
}
public class concreteIAM : IAM
{
public int ID { get; set; }
internal override void Save()
{
//save the object
}
//other staff for this particular class
}
public class MyList : List<IAM>
{
internal void Save()
{
foreach (IAM iam in this)
{
iam.Save();
}
}
//other staff for this particular class
}
Я размышлял над тем же вопросом здесь и наткнулся на этот вопрос...
Пока я думал об этом, я понял, что мне не нужен внутренний метод интерфейса.
Я могу получить к нему доступ через мой Конкретный класс и оставить Контракт для внешнего кода.
В вашем примере:
public interface IAM
{
int ID { get; set; }
}
public class concreteIAM : IAM
{
public int ID{get;set;}
internal void Save(){
//save the object
}
//other staff for this particular class
}
public class MyList : List<IAM>
{
public void Save()
{
foreach (concreteIAM iam in this)
{
iam.Save();
}
}
//other staff for this particular class
}