Почему я не могу объявить методы C# виртуальными и статическими?

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

Есть ли способ обойти это? Полагаю, я мог бы использовать синглтон .. HelperClass.Instance.HelperMethod () не намного хуже, чем HelperClass.HelperMethod (). Брауни указывает на любого, кто может указать на некоторые языки, поддерживающие виртуальные статические методы.

Редактировать: Ладно, я сумасшедший. Результаты поиска Google заставили меня подумать, что меня там немного не было.

Такой сценарий означает, что вам, вероятно, следует превратить это в объекты (не одиночные).

yfeldblum 29.10.2008 23:38

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

Luke 29.10.2008 23:45

Затем используйте одноэлементную инъекцию или внедрение зависимостей, в зависимости от того, что имеет смысл.

Chris Marasti-Georg 29.10.2008 23:53

Возможный дубликат Как реализовать виртуальные статические свойства?

peterh - Reinstate Monica 10.01.2018 14:13

В stackoverflow.com/a/66070907/6053778 я описываю шаблон, который, как мне кажется, может имитировать желаемое поведение.

Burakumin 08.02.2021 21:08
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
58
6
57 242
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

Ответ принят как подходящий

Виртуальные статические методы не имеют смысла. Если я вызываю HelperClass.HelperMethod();, почему я должен ожидать вызова некоторого случайного метода подкласса? Решение действительно не работает, когда у вас есть 2 подкласса HelperClass - какой из них вы бы использовали?

Если вы хотите иметь переопределяемые методы статического типа, вам, вероятно, следует использовать:

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

Выберите то решение, которое больше подходит для вашей ситуации.

-1 Потому что вы забыли про дженерики. T.Add (слева, справа); так много смысла. Не так ли?

SeeR 11.03.2010 02:32

И как бы вы переопределили свою реализацию в подклассе?

Chris Marasti-Georg 12.03.2010 21:03

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

Craig Gidney 08.04.2010 06:57

Да, но реализация подкласса не будет доступна из базового типа в статическом контексте, что и было задано в исходном вопросе.

Chris Marasti-Georg 13.04.2010 19:34

Да, они имеют смысл, это просто вопрос семантики. Я бы сказал, что если есть смысл объявлять статический метод внутри класса, имеет смысл разрешить переопределение его для каждого типа. См. Примеры Delphi в этой теме. Вы можете возразить, что «статика» имеет определенное значение; ну, в C он имеет совсем другое значение, чем, например, в C# и Java.

Flávio Etrusco 06.08.2013 00:50

@etrusco прав. В других языках, например в Python, есть виртуальные статические методы. На этих языках объект доступен для динамической отправки, но не передается методу (поскольку метод является статическим).

Neil G 25.01.2017 23:06

Да, это имеет смысл. Это не следует отмечать как ответ.

John Stock 11.01.2021 04:44

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

виртуальный метод будет «перезаписан» перегруженной функцией в зависимости от тип экземпляра.

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

Это не проблема поддержки, это концепция.

Обновлять: Здесь я ошибся (см. Комментарии):

So I doubt you will find any OOP-Language which will support virtual static methods.

Виртуальный метод может быть перезаписан по типу экземпляра. Все подклассы могут использовать одну и ту же реализацию перегруженной функции. Вот почему вы хотите сделать их ОБА статичными и виртуальными. Это позднее статическое связывание, доступное в PHP5.

Alex Weinstein 30.10.2008 22:46

Нет, здесь нет противоречия, это полностью вопрос поддержки компилятора, и многие языки, такие как Python, делают это. Они делают это, посещая первоклассные классы. То есть классы, которые сами по себе являются объектами, которые могут быть присвоены переменным и вызывать их методы. (Чтобы узнать больше, введите запрос "classmethod".)

Daniel Newby 11.03.2010 02:55

Object Pascal (Delphi и эквивалент Lazarus с открытым исходным кодом) всегда их поддерживал.

Carlo Sirna 28.10.2016 11:55

Я слышал, что Delphi поддерживает нечто подобное. Кажется, он делает это, создавая экземпляры объектов классов метакласса.

Я не видел, чтобы это работало, поэтому я не уверен, что это работает, или какой в ​​этом смысл.

P.S. Пожалуйста, поправьте меня, если я ошибаюсь, так как это не мой домен.

Действительно, это можно сделать в Delphi. Пример:

type
  TForm1 = class(TForm)
    procedure FormShow(Sender: TObject);
  end;

  TTestClass = class
  public
    class procedure TestMethod(); virtual;
  end;

  TTestDerivedClass = class(TTestClass)
  public
    class procedure TestMethod(); override;
  end;

  TTestMetaClass = class of TTestClass;

var
  Form1: TForm1;

implementation

{$R *.dfm}

class procedure TTestClass.TestMethod();
begin
  Application.MessageBox('base', 'Message');
end;

class procedure TTestDerivedClass.TestMethod();
begin
  Application.MessageBox('descendant', 'Message');
end;


procedure TForm1.FormShow(Sender: TObject);
var
  sample: TTestMetaClass;
begin
  sample := TTestClass;
  sample.TestMethod;
  sample := TTestDerivedClass;
  sample.TestMethod;
end;

Довольно интересно. Я больше не использую Delphi, но помню, как мог очень легко создавать различные типы элементов управления на настраиваемом холсте конструктора с помощью функции метакласса: класса управления, например. TButton, TTextBox и т. д. Были параметрами, и я мог вызвать соответствующий конструктор, используя фактический аргумент метакласса.

Вид заводской выкройки бедняги :)

Тот факт, что C# не поддерживает статические виртуальные методы, сводит меня с ума ... особенно учитывая, что C# был разработан тем же парнем, который проектировал delphi 10 лет назад (Андерс Хейлсберг)

Carlo Sirna 28.10.2016 11:54

Поскольку виртуальный метод использует определенный тип созданного объекта, чтобы определить, какую реализацию выполнить (в отличие от объявленного типа ссылочной переменной)

... и static, конечно, все о том, чтобы не заботиться о том, есть ли вообще экземпляр класса ...

Итак, они несовместимы.

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

Но, поскольку у вас уже есть эти статические методы, и теперь вам нужно их переопределить, вы можете решить свою проблему следующим образом: Добавьте к базовому классу методы виртуального экземпляра, которые просто делегируют статические методы, а затем переопределяют эти методы оболочки виртуального экземпляра (не статические) в каждом производном подклассе, если это необходимо ...

Вы не сошли с ума. То, что вы имеете в виду, называется поздним статическим связыванием; он был недавно добавлен в PHP. Есть отличная ветка, которая описывает это - здесь: Когда вам нужно использовать позднюю статическую привязку?

Фактически возможно объединить виртуальное и статическое для метода или члена, используя ключевое слово new вместо virtual.

Вот пример:

class Car
{
    public static int TyreCount = 4;
    public virtual int GetTyreCount() { return TyreCount; }
}
class Tricar : Car
{
    public static new int TyreCount = 3;
    public override int GetTyreCount() { return TyreCount; }
}

...

Car[] cc = new Car[] { new Tricar(), new Car() };
int t0 = cc[0].GetTyreCount(); // t0 == 3
int t1 = cc[1].GetTyreCount(); // t1 == 4

Очевидно, что значение TyreCount могло быть установлено в замещенном методе GetTyreCount, но это позволяет избежать дублирования значения. Получить значение можно как из класса, так и из экземпляра класса.

Может ли кто-нибудь найти действительно разумное использование этой функции?

@Mart: Я чуть не проголосовал за вас, пока не понял, что у вас просто плохой пример. Вместо статических полей измените пример, чтобы использовать статические методы, где производный класс вызывает метод базового класса, а затем делает что-то еще.

John Saunders 08.04.2010 06:51

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

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

public interface ISumable<T>
{
  static T Add(T left, T right);
}

и используйте это так:

public T Aggregate<T>(T left, T right) where T : ISumable<T>
{
  return T.Add(left, right);
}

Но сейчас это невозможно, поэтому делаю так:

    public static class Static<T> where T : new()
    {
      public static T Value = new T();
    }

    public interface ISumable<T>
    {
      T Add(T left, T right);
    }

    public T Aggregate<T>(T left, T right) where T : ISumable<T>, new()
    {
      return Static<T>.Value.Add(left, right);
    }

@SeeR: -1: Я исправлю это, если ошибаюсь, но это не решает исходную проблему.

John Saunders 08.04.2010 06:53

@John Saunders: Ему нужны статические виртуальные методы, но это невозможно - он должен использовать для этого методы экземпляра. Он также не хочет создавать экземпляр этого класса каждый раз, когда он хочет использовать этот статический (теперь экземплярный) метод - вот почему я создал класс Static <T>. Теперь у него будет только один экземпляр своего класса для всего приложения. Думаю, это приемлемый налог за такую ​​функциональность. Метод Aggregate <T> - это просто пример того, как он может его использовать. Итак, в итоге у нас есть статическая виртуальная замена в C# - разве не об этом запросе?

SeeR 09.04.2010 12:21

@SeeR: Ваш подход работает нормально, если рассматриваемый тип удовлетворяет ограничению new. Статические виртуальные методы позволили бы использовать такие конструкции безопасным для типов способом с типами, которые не удовлетворяли ограничению new. В качестве альтернативы можно было бы, чтобы класс Static не имел ограничения new, а скорее попытался бы использовать Reflection для создания объекта со специальным параметризованным конструктором. К сожалению, я не знаю способа сделать это типобезопасным.

supercat 01.11.2012 00:05

Я пришел из Delphi, и это одна из многих функций, которых мне очень не хватает в C#. Delphi позволит вам создавать ссылки на типизированные типы, и вы можете передавать тип производного класса везде, где требуется тип родительского класса. Такое обращение с типами как с объектами имело огромную пользу. В частности, позволяет определять метаданные во время выполнения. Я ужасно смешиваю синтаксис здесь, но в C# это будет выглядеть примерно так:

    class Root {
       public static virtual string TestMethod() {return "Root"; }
    }
    TRootClass = class of TRoot; // Here is the typed type declaration

    class Derived : Root {
       public static overide string TestMethod(){ return "derived"; }
    }

   class Test {
        public static string Run(){
           TRootClass rc;
           rc = Root;
           Test(rc);
           rc = Derived();
           Test(rc);
        }
        public static Test(TRootClass AClass){
           string str = AClass.TestMethod();
           Console.WriteLine(str);
        }
    } 

would produce: Root derived

Март понял это с помощью ключевого слова «новое». На самом деле я попал сюда, потому что мне нужна была такая функциональность, и решение Mart отлично работает. Фактически, я сделал это лучше и сделал свой метод базового класса абстрактным, чтобы заставить программиста предоставить это поле.

Мой сценарий был таким:

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

Вот частичный базовый класс HouseDeed:

public abstract class HouseDeed : Item
{
    public static int m_price = 0;
    public abstract int Price { get; }
    /* more impl here */
}

Теперь давайте посмотрим на два производных типа домов:

public class FieldStoneHouseDeed : HouseDeed
{
    public static new int m_price = 43800;
    public override int Price { get { return m_price; } }
    /* more impl here */
}

и...

public class SmallTowerDeed : HouseDeed
{
    public static new int m_price = 88500;
    public override int Price { get { return m_price; } }
    /* more impl here */
}

Как видите, я могу получить доступ к цене дома через тип SmallTowerDeed.m_price и экземпляр new SmallTowerDeed (). Price И будучи абстрактным, этот механизм заставляет программиста указывать цену для каждого нового производного типа дома.

Кто-то указал на то, что «статическое виртуальное» и «виртуальное» концептуально противоречат друг другу. Я не согласен. В этом примере статические методы не нуждаются в доступе к данным экземпляра, поэтому выполняются требования, чтобы (1) цена была доступна только через TYPE и (2) была указана цена.

Этот ответ только кажется косвенно связанным с вопросом. К тому же это довольно плохой пример. m_price является деталью реализации и ничего не добавляет, будучи частью HouseDeed. Было бы гораздо лучше удалить его из HouseDeed и сделать m_price приватным в обоих подклассах. Тот же результат без злоупотребления ключевым словом «новое».

Rob McCready 31.05.2011 03:38

Вы можете использовать новое ключевое слово

namespace AspDotNetStorefront
{
    // This Class is need to override StudioOnlineCommonHelper Methods in a branch
    public class StudioOnlineCommonHelper : StudioOnlineCore.StudioOnlineCommonHelper
    {
        //
        public static new void DoBusinessRulesChecks(Page page)
        {
            StudioOnlineCore.StudioOnlineCommonHelper.DoBusinessRulesChecks(page);
        }
    }
}

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

public class Base 
{
    //Other stuff

    public static void DoSomething()
    {
        Console.WriteLine("Base");
    }
}

public class SomeClass : Base
{
    public new static void DoSomething()
    {
        Console.WriteLine("SomeClass");
    }
}
public class SomeOtherClass : Base
{
}

Затем вы можете вызвать такие методы

Base.DoSomething(); //Base
SomeClass.DoSomething(); //SomeClass
SomeOtherClass.DoSomething(); //Base

Это может иметь непредвиденные последствия. Например, если в вашем базовом классе есть другой метод, вызывающий DoSomething, и вы вызываете SomeClass.ThatOtherMethod (), этот метод вызывает DoSomething базы, а не версию SomeClass. dotnetfiddle.net/scRWKD

user420667 08.11.2014 00:16

бросить NotImplementedException в базовый класс, чтобы убедиться, что ничто не вызывает это

Rob Sedgwick 08.06.2017 15:03

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

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

Вы не можете использовать модификаторы new, static или virtual для изменения метода переопределения.

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

Существует способ принудительного наследования «абстрактных статических» методов от абстрактного универсального класса. См. Следующее:

public abstract class Mother<T> where T : Mother<T>, new()
{
    public abstract void DoSomething();

    public static void Do()
    {
        (new T()).DoSomething();
    }

}

public class ChildA : Mother<ChildA>
{
    public override void DoSomething() { /* Your Code */ }
}

public class ChildB : Mother<ChildB>
{
    public override void DoSomething() { /* Your Code */ }
}

Пример (с использованием предыдущей Матери):

public class ChildA : Mother<ChildA>
{
    public override void DoSomething() { Console.WriteLine("42"); }
}

public class ChildB : Mother<ChildB>
{
    public override void DoSomething() { Console.WriteLine("12"); }
}

public class Program
{
    static void Main()
    {
        ChildA.Do();  //42
        ChildB.Do();  //12
        Console.ReadKey();
    }
}

Это не так уж и хорошо, поскольку вы можете наследовать только от одного абстрактного класса, и он попросит вас проявить снисходительность к вашей реализации new ().

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

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