Хобрук: Ваш путь к мастерству в программировании

Могу ли я ограничить общий метод более чем одним интерфейсом?

У меня есть общий метод

public static void DoSomething<T>()
{...}

. Теперь я хочу ограничить этот T.

public static void DoSomething<T>() where T: IInterface1
{...}

Но то, что я действительно хочу, это разрешить несколько интерфейсов, что-то вроде

public static void DoSomething<T>() where T: IInterface1, IInterface2
{...}

Но это не работает. Компилятор говорит что-то вроде

Нет неявного преобразования из IInterface1 в IInterface2

Нет неявного преобразования из IInterface2 в IInterface1.

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

Какие возможности у меня есть, чтобы разрешить несколько интерфейсов?

Спасибо, Тоби

Изменить: вот что я хотел сделать. Я разрабатываю надстройку Outlook. Я использую этот фрагмент кода довольно часто.

    public static object GetItemMAPIProperty<T>(AddinExpress.MAPI.ADXMAPIStoreAccessor adxmapiStoreAccessor, object outlookItem, uint property) where T: Outlook.MailItem, Outlook.JournalItem
    {
        AddinExpress.MAPI.MapiItem mapiItem;
        mapiItem = adxmapiStoreAccessor.GetMapiItem(((T)outlookItem));
        return mapiItem != null ? mapiItem.GetProperty(property) : null;
    }

Метод GetMapiItem принимает объект, если он является одним из элементов Outlook (Журнал, Почта, Контакты,...). Вот почему я ограничивал T. Потому что это не может быть, скажем, Outlook.MAPIFolder.

Нет, я изменил метод на

    public static object GetItemMAPIProperty<T>(AddinExpress.MAPI.ADXMAPIStoreAccessor adxmapiStoreAccessor, T outlookItem, uint property)
    {
        AddinExpress.MAPI.MapiItem mapiItem;
        mapiItem = adxmapiStoreAccessor.GetMapiItem(((T)outlookItem));
        return mapiItem.GetProperty(property);
    }

но разработчик (в данном случае я) может присвоить ему любой тип, потому что метод GetMapiItem принимает объект. Я надеюсь, что в этом есть смысл. Я не уверен, подходит ли это для этого примера, но я думаю, что ограничение универсального метода несколькими типами (с ИЛИ) может быть хорошей идеей.

28.07.2009

  • На какой фреймворк вы ориентируетесь, компилируете ли вы его с помощью Visual Studio и какой версии? У меня есть VS 2008 с .NET 3.5, и все вышеперечисленное прекрасно компилируется. Вы уверены, что то, что вы даете нам в качестве примера, это то, что у вас есть. 28.07.2009
  • потому что таким образом вы говорите компилятору, что T должен быть IInterface1, а IInterface2 нет или 28.07.2009
  • код, который он предоставляет, работает, ему просто нужно сказать, что метод принимает параметр типа IInterface1 OR IInterface2 28.07.2009
  • @Ivan: у меня такая же настройка, и если я не вызываю метод, он компилируется нормально. Однако, если я попытаюсь вызвать его, он жалуется, что тип, который я ему даю, не является подклассом обоих. Данным where является И, ОП хочет ИЛИ. 28.07.2009
  • Битый на полминуты, razzum frazzum mmbl grmbl... 28.07.2009
  • Тобиас: Вы хотите сказать, что у T есть оба интерфейса, или это будет один из них? 28.07.2009
  • Я хочу сказать, что T может быть IInterface1 или IInterface2. Я использую VS2008, но компилирую для .NET 2.0. 28.07.2009
  • @Tobias, не могли бы вы опубликовать более подробную информацию о проблеме, которую вы пытаетесь решить? Вполне вероятно, что есть другой подход, который дает вам желаемое повторное использование, но невозможно предложить что-либо, основываясь на таком конкретном вопросе о языковой функции. 28.07.2009
  • Итак, предположим, что есть способ сделать это. Предположим, интерфейс 1 имеет метод Foo, а интерфейс 2 имеет панель методов. Вы не можете вызвать Foo для T, потому что это может быть i2, и вы не можете вызвать Bar для T, потому что это может быть i1. Следовательно, эта функция помешает вам что-либо делать с T. Вот почему мы ее не реализовали. 29.07.2009

Ответы:


1

Это прекрасно компилируется для меня:

interface I1 { int NumberOne { get; set; } }
interface I2 { int NumberTwo { get; set; } }

static void DoSomething<T>(T item) where T:I1,I2
{
    Console.WriteLine(item.NumberOne);
    Console.WriteLine(item.NumberTwo);
}

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

28.07.2009
  • Конечно, это работает в C#, он просто делает то, что должен: он определяет метод, который принимает только item, который поддерживает ОБА интерфейса. 28.07.2009
  • О, комментарий, на который я отвечал, исчез. 28.07.2009
  • @Earwicker - это был мой комментарий. Я случайно оставил это на неправильном ответе. 28.07.2009
  • @Earwicker - ты прав. Я упускаю суть..., нова хочет ИЛИ. Вам нужны два отдельных метода @James - упс. Я понимаю вашу точку зрения. 28.07.2009

  • 2

    Пусть Interface1 и Interface2 происходят от одного и того же базового интерфейса. Бывший:

        public static void DoSomething<T>() where T : ICommon
        {
            //...
        }
    
        public interface IInterface1 : ICommon
        {}
    
        public interface IInterface2 : ICommon
        { }
    
        public interface ICommon
        { }
    

    Преимущество этого способа заключается в том, что вам не нужно постоянно обновлять определение DoSomething() каждый раз, когда вы добавляете новый интерфейс, наследуемый от ICommon.

    Изменить: если у вас нет контроля над интерфейсами, у вас есть несколько вариантов. Вот одна вещь, которую вы могли бы сделать...

        protected static class DoSomethingServer<T1> where T1 : class
        {
    
            //Define your allowed types here
            private static List<Type> AllowedTypes = new List<Type> {
                typeof(IInterface1),
                typeof(IInterface2)
            };
    
            public static MethodInvoker DoSomething()
            {
                //Perform type check
                if (AllowedTypes.Contains(typeof(T1)))
                {
                    return DoSomethingImplementation;
                }
                else
                {
                    throw new ApplicationException("Wrong Type");
                }
            }
    
            private static void DoSomethingImplementation()
            {
                //Actual DoSomething work here
                //This is guaranteed to only be called if <T> is in the allowed type list
            }
        }
    

    Использовать как таковой:

    DoSomethingServer<IInterface1>.DoSomething();
    

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

    28.07.2009
  • это может быть даже пустой интерфейс-заполнитель, IStorable 28.07.2009
  • Из вопроса: я подумал о том, чтобы позволить классам реализовать общий интерфейс, на который я могу ссылаться, но у меня нет доступа к классам. Кажется маловероятным, что интерфейсы могли быть изменены тогда. 28.07.2009

  • 3

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

    Вы даже не можете сказать (заимствуя у надера!):

        interface I1 { int NumberOne { get; set; } }
        interface I2 { int NumberTwo { get; set; } }
    
        static void DoSomething<T>(T item) where T : I1
        {
            Console.WriteLine(item.NumberOne);
        }
    
        static void DoSomething<T>(T item) where T : I2
        {
            Console.WriteLine(item.NumberTwo);
        }
    
        static void DoSomething<T>(T item) where T : I1, I2
        {
            Console.WriteLine(item.NumberOne);
            Console.WriteLine(item.NumberTwo);
        }
    

    Это дало бы компилятору возможность обрабатывать все возможности без двусмысленности. Но чтобы помочь с управлением версиями, C# старается избегать ситуаций, когда добавление/удаление метода меняет применимость другого метода.

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

    28.07.2009
  • что, если DoSomething не должен принимать параметр? 28.07.2009
  • Не имеет значения. Вам нужно написать метод для каждого интерфейса и дать методам разные имена. 28.07.2009
  • Наследование ИЛИ имеет обходной путь с грядущей утиной типизацией и C# 4.0 28.07.2009
  • +1 Вам нужно написать два метода с разными именами для обработки двух интерфейсов. 28.07.2009

  • 4

    один из способов - создать дополнительный интерфейс, который расширяет оба интерфейса, Interface1 и 2. Затем вы помещаете этот интерфейс вместо двух других.

    это один из способов сделать это в java; если я правильно помню, это должно работать и в С#

    надеюсь, это поможет.

    привет, тоби тоже :P

    28.07.2009
  • Вы получите ошибку времени компиляции: нет неявного преобразования ссылки из (IInterface1||IInterface2) в (имя интерфейса, который расширяет оба). 28.07.2009

  • 5
        public interface IInterfaceBase
        {
    
        }
        public interface IInterface1 : IInterfaceBase
        {
          ...
        }
        public interface IInterface2 : IInterfaceBase
        {
          ...
        } 
    
        public static void DoSomething<T>() where T: IInterfaceBase
        {
        }
    

    Если вы хотите, чтобы T был IInterface1 или IInterface2, используйте приведенный выше код.

    28.07.2009
  • +1 это сработает, если он просто ищет, чтобы оба типа имели некоторые общие элементы. 28.07.2009

  • 6

    Опираясь на то, что сказал Эрвикер... имена - не единственный путь. Вы также можете изменить сигнатуры методов...

    public interface I1 { int NumberOne { get; set; } }
    public interface I2 { int NumberTwo { get; set; } }
    
    public static class EitherInterface
    {
        public static void DoSomething<T>(I1 item) where T : I1
        {
            Console.WriteLine("I1 : {0}", item.NumberOne);
        }
    
        public static void DoSomething<T>(I2 item) where T : I2
        {
            Console.WriteLine("I2 : {0}", item.NumberTwo);
        }
    }
    

    Что при проверке следующим образом:

    public class Class12 : I1, I2
    {
        public int NumberOne { get; set; }
        public int NumberTwo { get; set; }
    }
    
    public class TestClass
    {
        public void Test1()
        {
            Class12 z = new Class12();
            EitherInterface.DoSomething<Class12>((I1)z);
            EitherInterface.DoSomething<Class12>((I2)z);
        }
    }
    

    Дает этот вывод:

    I1 : 0
    I2 : 0
    

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

    28.07.2009
  • Я мог бы это сделать, но мне пришлось бы написать реализацию для каждого интерфейса. Вот почему я хотел использовать дженерики в первую очередь. 28.07.2009
  • Новые материалы

    Управление DOM для чайников вроде меня
    Одной из первых вещей, которую мы рассмотрели, когда начали изучать Javascript во Flatiron, была модель DOM. Кто он? Чем он занимается? Он больше машина, чем человек? Ну да довольно много. ДОМ..

    Что такое структура данных?
    Структура данных хранит и извлекает данные. Все, что обеспечивает эти две функции, является структурой данных . Период. Вы можете пропустить оставшуюся часть статьи, если ответ..

    мои январские чтения по программированию
    Эрик Эллиот Программирование приложения JavaScript Эл Свейгарт «Автоматизируйте скучные вещи с помощью Python» Прогрессивное веб-приложение Google..

    Создание ассоциаций секвелизации с помощью инструмента командной строки Sequelize
    Sequelize - популярный, простой в использовании инструмент объектно-реляционного сопоставления (ORM) JavaScript, который работает с базами данных SQL. Довольно просто начать новый проект с..

    Искусственный интеллект в юридической отрасли - пример прогнозирования судебных решений с помощью глубокого обучения
    На протяжении всей истории люди полагались на суды, присяжных, королей и королев в отправлении правосудия. Сегодня способность судов обеспечивать справедливое и быстрое правосудие для своих..

    Введение в машинное обучение для обнаружения аномалий (часть 1)
    Тщательно созданный, тщательно спроектированный ресурс для специалистов по данным. Часть 1 Главы 03 из Руководства по машинному обучению для обнаружения аномалий Внимание! Прежде чем вы..

    Начало работы с Pulumi в Digital Ocean
    Цифровой океан (ДО) — отличная альтернатива многим другим поставщикам облачных услуг. DO предоставляет простой и понятный пользовательский интерфейс, упрощающий управление инфраструктурой и..