Почему C#(или.NET) не должны позволять нам помещать статический / разделяемый метод в интерфейс?
Почему C#(или.NET) не должны позволять нам помещать статический / разделяемый метод в интерфейс?
казалось бы, дубликат отсюда. но моя идея немного отличается, я просто хочу добавить помощника для моих плагинов (интерфейс)
не должен ли C# хотя бы допустить эту идею?
namespace MycComponent
{
public interface ITaskPlugin : ITaskInfo
{
string Description { get; }
string MenuTree { get; }
string MenuCaption { get; }
void ShowTask(Form parentForm);
void ShowTask(Form parentForm, Dictionary<string, object> pkColumns);
ShowTaskNewDelegate ShowTaskNew { set; get; }
ShowTaskOpenDelegate ShowTaskOpen { set; get; }
// would not compile with this:
public static Dictionary<string, ITaskPlugin> GetPlugins(string directory)
{
var l = new Dictionary<string, ITaskPlugin>();
foreach (string file in Directory.GetFiles(directory))
{
var fileInfo = new FileInfo(file);
if (fileInfo.Extension.Equals(".dll"))
{
Assembly asm = Assembly.LoadFile(file);
foreach (Type asmType in asm.GetTypes())
{
if (asmType.GetInterface("MycComponent.ITaskPlugin") != null)
{
var plugIn = (ITaskPlugin)Activator.CreateInstance(asmType);
l.Add(plugIn.TaskName, plugIn);
}
}
}
}
return l;
} // GetPlugins. would not compile inside an interface
}
/* because of the error above, I am compelled to
put the helper method in a new class. a bit overkill when the method should
be closely coupled to what it is implementing */
public static class ITaskPluginHelper
{
public static Dictionary<string, ITaskPlugin> GetPlugins(string directory)
{
var l = new Dictionary<string, ITaskPlugin>();
foreach (string file in Directory.GetFiles(directory))
{
var fileInfo = new FileInfo(file);
if (fileInfo.Extension.Equals(".dll"))
{
Assembly asm = Assembly.LoadFile(file);
foreach (Type asmType in asm.GetTypes())
{
if (asmType.GetInterface("MycComponent.ITaskPlugin") != null)
{
var plugIn = (ITaskPlugin)Activator.CreateInstance(asmType);
l.Add(plugIn.TaskName, plugIn);
}
}
}
}
return l;
} // GetPlugins
} // ITaskPluginHelper
}
7 ответов
Идея интерфейса состоит в том, чтобы представлять договор, а не реализацию.
Я не могу вспомнить, действительно ли IL допускает статические методы с реализациями в интерфейсах - у меня есть хитрое подозрение, что это так, - но это несколько запутывает концепцию.
Я понимаю вашу точку зрения - иногда полезно знать, какие вспомогательные методы доступны, которые связаны с интерфейсом (и методы расширения там особенно актуальны), но я бы лично хотел поместить их в отдельный класс в любом случае, просто чтобы сохранить ментальный модель чистая.
Я сталкивался с этим несколько раз и провел некоторое исследование. Грустная часть, IL фактически поддерживает это. Я так расстроился, что написал об этом в блоге. Вы можете найти это здесь.
Проверьте мою запись в блоге о статических методах, реализованных в интерфейсах (извините за бесстыдную ссылку на себя)
[удалена неработающая ссылка http:/... ]
Сайт dotnetjunkies ткнул totaldevpro... так что кэшированная версия Google является единственной доступной
Изменить: я вставил кэшированную версию ниже, я нашел:
[...]
Используйте ILAsm для компиляции следующего:
.assembly extern mscorlib {
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
.assembly MaLio.StaticInterface{
.hash algorithm 0x00008004
.ver 0:1:0:0
}
.module MaLio.StaticInterface.dll
.imagebase 0x00400000
.file alignment 0x00001000
.stackreserve 0x00100000
.subsystem 0x0003
.corflags 0x00000001
.class interface public abstract auto ansi MaLio.IMyInterface {
.method public hidebysig newslot abstract virtual instance void DoInstanceWork() cil managed {
}
.method public hidebysig static void DoStaticWork() cil managed {
ldstr "Static"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
}
.class public auto ansi beforefieldinit MaLio.MyClass extends [mscorlib]System.Object implements MaLio.IMyInterface {
.method public hidebysig newslot virtual final instance void DoInstanceWork() cil managed {
ldstr "Instance"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed {
ldarg.0
call instance void [mscorlib]System.Object::.ctor()
ret
}
}
Этот код тогда можно назвать
System.Type myInterface = typeof(MaLio.IMyInterface);
// show that we really are dealing with an interface
if (myInterface.IsInterface) {
System.Reflection.MethodInfo staticMethod = myInterface.GetMethod("DoStaticWork");
staticMethod.Invoke(null, null);
}
Intellisense (VS) не работает здесь, как ожидалось. Он распознал статический метод как метод экземпляра интерфейса, и код (если он следует подсказкам intellisense) выглядит все по порядку, как если бы он собирался компилироваться. Компилятор C# (MS C#) не компилирует код, поскольку C# не поддерживает реализованные статические методы на интерфейсах и может быть вызван из C# только через отражение.
Я не тестировал с другими IDE, такими как SharpDevelop ... так что пока не знаю, как он справится с этой ситуацией.
Для вашей цели будет намного лучше отделить интерфейс плагина от реализации загрузчика плагинов: это сделает ваш дизайн намного менее связанным и более связным (таким образом уменьшая сложность).
Что касается "статических методов в интерфейсе", посмотрите это.
И как примечание: вы действительно не хотите изобретать еще одну архитектуру плагинов: взгляните на MEF.
Интерфейс - это просто интерфейс. Он не предназначен для описания поведения. Когда класс реализует интерфейс, он просто говорит: "Я обещаю, что предоставлю методы / события / и т. Д. С этими сигнатурами".
Вам нужен интерфейс без статического метода и абстрактный базовый класс, который реализует интерфейс и статический метод. Тогда другие классы могут наследовать от базового класса и изменять реализации метода интерфейса. Но даже это сомнительный дизайн.
Цель интерфейса - объявить интерфейс объекта, через который он может быть доступен. В связи с тем, что это является его единственной целью, не имеет смысла разрешать размещение кода в интерфейсе. Если вы все еще хотите добавить некоторый код в интерфейс, вы можете использовать методы расширения.
Статические методы связаны с типом, в котором они объявлены, и не имеют отношения к переопределению. Если бы вы могли присоединить статический метод к интерфейсу, вам бы пришлось ссылаться на него через сам интерфейс, например ITaskPlugin.GetPlugins(...)
То, что вы хотите сделать, это либо:
1) Поместите ваш метод в абстрактный базовый класс, так как интерфейсы не предназначены для хранения кода реализации, или
2) Создайте метод расширения, который применяется к интерфейсу, и тогда у вас будет доступ к нему без использования базового класса.