Внутренний член в интерфейсе

У меня есть список объектов, реализующих интерфейс, и список этого интерфейса:

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 + Сохранить. Я все еще думаю ... Есть еще идеи?

Если вы хотите сохранить метод Save в конкретном классе AM, лучше всего, вероятно, использовать абстрактный базовый класс с внутренним абстрактным методом Save, как это было предложено Натаном В.

Andrew Kennan 16.12.2008 02:51

спасибо за ответы, но ни один из них не является именно тем, что мне нужно: интерфейс должен быть общедоступным, потому что это подпись, которую будет использовать клиент извне dll, вместе с идентификатором и другими свойствами, которые я не потрудился написать в примере, чтобы не усложнять. Эндрю, я не думаю, что решение состоит в создании фабрики для создания другого объекта, который будет содержать элементы IAM + Сохранить. Я все еще думаю ... Есть еще идеи?

fer 15.12.2008 09:59

лучше поздно, чем никогда, но это может помочь: stackoverflow.com/a/18944374/492

CAD bloke 14.04.2015 01:41
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
38
3
31 691
12

Ответы 12

Я не думаю, что вам следует использовать здесь интерфейс, возможно, вам следует использовать абстрактную базу, например:

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();
        }
    }
}

Довольно ограниченно, поскольку, к сожалению, у нас нет множественного наследования.

jeromej 26.01.2021 11:54

Если вы не хотите, чтобы внешние вызывающие абоненты могли вызывать ваш метод Save (), почему бы не сделать весь класс конкретногоIAM внутренним?

Или, если вы хотите, чтобы класс был общедоступным, но не интерфейс, сделайте весь интерфейс внутренним. Я думаю, что внутренний интерфейс можно добавить к общедоступному классу (но я не пробовал ...)

Возможно, вы хотите разделить сохранение ваших элементов в другой набор классов, которые являются внутренними для вашей сборки:

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)
    }
  }
}

Члены интерфейса должны быть общедоступными ... все остальное, и вы должны думать, если вам что-то еще нужно. В этом случае вы

  • хотите, чтобы Save был членом интерфейса
  • хотите, чтобы сохранение было разрешено только через другой класс MyList, который находится в той же сборке
  • запретить вызов Save извне (из других классов вне родительской сборки)

Отложив в сторону мысли о дизайне, вы можете сделать это с помощью явной реализации интерфейса.

    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() и другие внутренние методы. Возможно, этот второй интерфейс может даже унаследовать от первого. Тогда ваши собственные внутренние объекты будут реализовывать оба интерфейса. Таким образом вы даже можете различать внешние объекты (те, у которых нет внутреннего интерфейса) и внутренние объекты.

Уф! кто-то на самом деле отступает и спрашивает, почему, вместо того, чтобы пытаться закодировать по кругу ...

Dan Vinton 16.12.2008 03:03

Простой сценарий: в сборке есть классы Thing1 и Thing2, которые по разным причинам не могут использовать общий базовый тип, но имеют общий интерфейс IThing. Он желает предоставить объекты внешнего кода, которые могут быть переданы обратно в код, которому требуется IThing, но он хочет гарантировать, что любой полученный IThing действительно будет правильно реализовывать контракт IThing, запрещая любые реализации извне сборки. Аналогичного эффекта можно было бы добиться, используя класс-оболочку или структуру, но код был бы чище, если бы этого не требовалось.

supercat 31.10.2011 20:36

Боюсь, я не могу придумать, как добиться этого на C#. Однако есть много вещей, которые не могут быть достигнуты на C# (или на любом другом языке, если на то пошло). Вот почему существуют кодовые контракты.

Vilx- 01.11.2011 02:04

Я знаю, что прошло 5 лет, но чувствую необходимость ответить. То, что он просит, эквивалентно «внутреннему защищенному абстрактному». У него все те же побочные эффекты (нет). Разработчик будет вынужден его реализовать. Его можно будет вызвать только из сборки, которой принадлежит определение метода, и из самого разработчика. Точно так же. Просто не поддерживается.

Jerome Haltom 07.08.2013 09:15

@wasabi - интерфейс - это список методов общественный, которые должен иметь класс. Это его цель. Он не заботится (и никогда не будет заботиться, даже в будущих версиях .NET) о частных / защищенных / внутренних методах.

Vilx- 07.08.2013 20:09

@ Vilx - Я прекрасно понимаю, что такое интерфейс ЯВЛЯЕТСЯ. Я хочу сказать, что то, о чем просит OP, не имеет побочных эффектов. Возможно, этого не существует в языке, и я думаю, мы все это знаем, но он МОЖЕТ существовать в языке. И я не предполагаю, что так будет. Но могло.

Jerome Haltom 21.08.2013 02:54

@wasabi - Итак ... это будет общедоступный интерфейс, но с внутренними членами, поэтому его можно реализовать только внутри? Я не уверен, каковы будут последствия этого. Мне почему-то «кажется», что это вызовет серьезные конфликты в CLR, но я не знаю достаточно, чтобы оправдать это внутреннее чувство. Было бы интересно услышать мнение кого-то знающего по теме.

Vilx- 21.08.2013 10:26

Только с точки зрения варианта использования имеет смысл определить контракт, в котором указано, что метод будет существовать, но его можно будет вызывать только из объектов, хранящихся в той же сборке. Конечно, хотелось бы иметь фичу.

Ignacio Soler Garcia 07.02.2014 17:35

У меня такая же проблема несколько лет спустя, у меня есть движок, который требует от Models предоставлять внутренний метод Render (), а не публичный метод, внутренний метод, потому что код игры не отвечает за рисование. И все же интерфейсы не предоставляют внутренних контрактов. Это ужасный недостаток. Нет абсолютно никакой причины, контракт, предоставляемый движком, не может сказать - «вы должны предоставить внутренний метод рисования, потому что он мне нужен, и я понимаю, что у вас есть собственный код рисования». И я - игровой движок, использующий систему компонентов сущностей, и мне не нравится слишком много наследования »

Gavin Williams 03.04.2015 18:39

@ Vilx-: "интерфейс, который является общедоступным, но имеет внутренние элементы, поэтому он может быть реализован только внутри?" - точно. Это то же самое, что и незапечатанный класс с внутренним конструктором: Visible, но он может быть разделен только на внутренние подклассы. Я регулярно использую этот шаблон, когда хочу предоставить пользователям API иерархию объектов, которую они не могут изменять извне (или только в определенных «открытых» узлах, а не прямо в базовом классе). Я неоднократно хотел сделать что-то подобное с интерфейсами.

O. R. Mapper 10.04.2017 23:35

@ O.R.Mapper - Итак, по сути, вам нужен интерфейс, который может быть передан внешнему коду, но может быть реализован только внутренними классами. Что ж ... Я вижу в этом пользу. К сожалению, я недостаточно хорошо разбираюсь во внутреннем устройстве .NET и его дизайнерских решениях, чтобы сказать Зачем, что в настоящее время это невозможно. Я подозреваю, что на это есть причина.

Vilx- 11.04.2017 12:35

@ Vilx-: Да, именно этого я и хочу. Я не уверен, что есть техническая причина, учитывая, что то же самое может можно сделать с внутренними конструкторами или с внутренними абстрактными методами, как описано выше. Я полагаю, что это просто случай отказа от дополнительной сложности в компиляторе C# (или даже в определении интерфейсов во время выполнения CLI).

O. R. Mapper 11.04.2017 12:46

@ O.R.Mapper - Некоторые догадки: могут быть проблемы с совместимостью с COM (.NET ОЧЕНЬ дружелюбен к COM, и многие концепции .NET сопоставляются непосредственно с концепциями COM) или другими языками, с которыми они хотели бы легко взаимодействовать; интерфейсы и классы могут быть очень разными с точки зрения низкого уровня (MSIL), и введение модификаторов может действительно усложнить ситуацию; также могут быть идеологические проблемы (например, если вы явно определяете интерфейс как «публичный контракт» или что-то в этом роде, то внутренние участники пойдут против этого).

Vilx- 11.04.2017 13:58

@ O.R.Mapper - И последнее, но не менее важное - каждая функция начинается с -100 баллов. На разработку доступно ограниченное количество времени, а список желаний всегда намного длиннее, чем то, что можно реально осуществить. Итак, вам нужно выбрать несколько наиболее полезных вещей. Возможно, эта функция просто не использовалась. Черт возьми, свойства расширения востребованы еще шире, и даже они еще не получил зеленый свет (AFAIK).

Vilx- 11.04.2017 14:05

Однако ваш ответ не имеет ничего общего с доступом к этой реализации. Поскольку я мог бы просто создать абстрактный класс, полностью заполнить контракт и предоставить компилятору различные требования доступа для рассмотрения. Интерфейс должен работать точно так же.

zezba9000 28.08.2018 02:39

@ zezba9000 - Я не понимаю, что вы имеете в виду. Вы не можете изменить модификаторы доступа при переопределении.

Vilx- 28.08.2018 10:48

Пойдите с двумя интерфейсами:

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 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 InternalIAM
{
    void Save();
}

public class concreteIAM : InternalIAM
{
    void InternalIAM.Save()
    {
    }
}

Спасибо, всегда хотел знать, как реализовать внутренний интерфейс в общедоступном классе.

yoyo 26.02.2015 02:33

Проблема в том, что у вас не может быть общедоступных методов, использующих InternalIAM в качестве типа параметра.

O. R. Mapper 10.04.2017 23:37

Не идеальный, но пока лучший компромисс.

jeromej 26.01.2021 11:53

Я думаю, что лучше всего было бы разделить внутренние и общедоступные члены на два отдельных интерфейса. Если вы наследуете интерфейсы, вы все равно можете объявлять члены публично, но видимость внутренних членов будет определяться видимостью самого интерфейса.

using System;

public interface IPublic
{
    void Public();
}

internal interface IInternal : IPublic
{
    void Internal();
}

public class Concrete : IInternal
{
    public void Internal() { }

    public void Public() { }
}

видимость членов продиктована реализацией члена. Если вы создаете экземпляр объекта Concrete, вы можете вызвать его метод Public.

fer 16.12.2008 04:08

это хороший момент - я представил какой-то фабричный метод, который будет возвращать Concrete как IPublic, таким образом скрывая всех остальных членов

Andrew Hare 18.12.2008 05:33

Тогда также можно было бы сочетать с явной реализацией, но у нее есть свои плюсы и минусы.

jeromej 26.01.2021 11:56

Это работает большой с внедрением зависимостей в .NET Core.

Andy 23.02.2021 08:18

Мне было интересно здесь по той же проблеме, и наткнулась на этот вопрос ...

Когда я подумал об этом, я понял, что мне вообще не нужен метод внутренний в интерфейсе.

Я могу получить к нему доступ через свой конкретный класс, и оставьте Контракт на Внешний код.

В вашем примере:

    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
    }

-1, приводит к ошибке усложнения.'ConcreteIAM 'не реализует член интерфейса' IAM.Save () '

Kalyanaraman Santhanam 02.10.2014 03:22

Когда кому-то приходится сказать: «Надеюсь, это поможет», обычно этого не происходит. Такие как здесь.

nathanchere 26.07.2016 12:04

как по многочисленным просьбам, исправил пример ... интерфейс никогда не должен изначально определять сохранение

Tomer W 26.07.2016 15:32

Об этом я и говорю в своей статье Друзья и участники внутреннего интерфейса бесплатно с кодированием интерфейсов.

Суть из статьи

public class Internal
{
    internal Internal() { }
}

public interface IAM
{
    int ID { get; set; }
    void Save(Internal access);
}

С этого момента только ваша сборка может вызывать метод Save(), поскольку экземпляр класса Internal может быть создан только вашей сборкой.

Если они каким-то образом не овладеют объектом, не так ли? Это только мешает им создать его экземпляр, а не получить доступ к его общедоступному полю, если они получили доступ к самому объекту. Кроме того, я предполагаю, что вы имели в виду Internal, чтобы наследовать от IAM и реализовать ID и сохранить.

jeromej 26.01.2021 11:58

У меня была похожая ситуация, и я подумал, что это может помочь. (не уверен, не нужен ли это сейчас или нет)

Для меня это законный случай: предположим, что есть интерфейс, который имеет некоторый 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
}

Другие вопросы по теме