Явная реализация интерфейса и generic метод
От: igor-booch Россия  
Дата: 30.07.20 09:18
Оценка:
using System;

namespace ConsoleApp17
{
    class Program
    {
        static void Main(string[] args)
        {
            Class @object = new Class();
            IBase @base = @object;
            IDerived1 derived1 = @object;
            IDerived2 derived2 = @object;

            @base.Do();
            derived1.Do();
            derived2.Do();

            Do<IBase>(@base);
            Do<IDerived1>(derived1);
            Do<IDerived2>(derived2);

            Console.ReadLine();
        }

        public static void Do<TClass>(TClass @object) where TClass : IBase
        {
            @object.Do();
        }

    }

    public class Class : IDerived1, IDerived2
    {
        #region Implementation of IBase

        void IBase.Do()
        {
            Console.WriteLine("IBase");
        }

        void IDerived1.Do()
        {
            Console.WriteLine("IDerived1");
        }

        void IDerived2.Do()
        {
            Console.WriteLine("IDerived2");
        }
        #endregion
    }

    public interface IBase
    {
        void Do();
    }

    public interface IDerived1 : IBase
    {
        new void Do();
    }

    public interface IDerived2 : IBase
    {
        new void Do();
    }
}



Выводит:

IBase
IDerived1
IDerived2
IBase
IBase
IBase



Есть ли способ сделать так, чтобы вывод был:

IBase
IDerived1
IDerived2
IBase
IDerived1
IDerived2


???
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Отредактировано 30.07.2020 9:18 igor-booch . Предыдущая версия .
Re: Явная реализация интерфейса и generic метод
От: ksg71 Германия  
Дата: 30.07.20 11:05
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>[code]

IB>using System;

не уверен что нужно это, но так


public static void Do<TClass>(TClass @object) where TClass : IBase
{
    typeof(TClass).GetMethod("Do")?.Invoke(@object, null);
    //@object.Do();
}
Das Reich der Freiheit beginnt da, wo die Arbeit aufhört. (c) Karl Marx
Re: Явная реализация интерфейса и generic метод
От: fmiracle  
Дата: 30.07.20 11:45
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>

IB>    public static void Do<TClass>(TClass @object) where TClass : IBase
IB>    {
IB>        @object.Do();
IB>    }

IB>    public interface IBase
IB>    {
IB>        void Do();
IB>    }

IB>    public interface IDerived1 : IBase
IB>    {
IB>        new void Do();
IB>    }

IB>    public interface IDerived2 : IBase
IB>    {
IB>        new void Do();
IB>    }
IB>}

IB>


У тебя 3 совсем разных метода Do, никак не связанных между собой. В ограничении генерика ты задаешь, что у переданного TClass должен быть метод IBase.Do. Вот его компилятор и использует.

Средствами компилятора, боюсь, сделать, то что ты хочешь, никак не получится. А через рефлекшен — можно, именно как было написано выше:

public static void Do<TClass>(TClass @object) where TClass : IBase
{
typeof(TClass).GetMethod("Do")?.Invoke(@object, null);
//@object.Do();
}


Тут происходит поиск метода именно у переданного класса (IDerived1, IDerived2), и уже этот метод вызывается у объекта.
Re: Явная реализация интерфейса и generic метод
От: Sinclair Россия https://github.com/evilguest/
Дата: 30.07.20 17:14
Оценка:
Здравствуйте, igor-booch, Вы писали:
Может, вам dynamic попробовать?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: Явная реализация интерфейса и generic метод
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 31.07.20 07:26
Оценка:
Здравствуйте, igor-booch, Вы писали:

попробуй
public static void Do<TClass>(TClass @object) where TClass : IBase
{
switch(@object)
{
case IDerived2 d2:@d2.Do();break;
case IDerived1 d1: d1.Do();break;
default @object.Do();break;
}
и солнце б утром не вставало, когда бы не было меня
Re: Сделал так
От: igor-booch Россия  
Дата: 31.07.20 08:24
Оценка:
Всем спасибо, c рефлешеном, понятно, что можно всё, но производительность страдает,
пока сделал так

using System;

namespace ConsoleApp17
{
    class Program
    {
        static void Main(string[] args)
        {
            Class @object = new Class();
            IBase @base = @object;
            IDerived1 derived1 = @object;
            IDerived2 derived2 = @object;

            @base.Do();
            derived1.Do();
            derived2.Do();

            Do(@base);
            Do(derived1);
            Do(derived2);

            Console.ReadLine();
        }

        public static void Do<TClass>(TClass @object) 
            where TClass : IBase

        {
            if (typeof(TClass) == typeof(IDerived1))
                ((IDerived1) @object).Do();
            else if (typeof(TClass) == typeof(IDerived2))
                ((IDerived2) @object).Do();
            else
                ((IBase) @object).Do();
        }

    }

    public class Class : IDerived1, IDerived2
    {
        #region Implementation of IBase

        void IBase.Do()
        {
            Console.WriteLine("IBase");
        }

        void IDerived1.Do()
        {
            Console.WriteLine("IDerived1");
        }

        void IDerived2.Do()
        {
            Console.WriteLine("IDerived2");
        }
        #endregion
    }

    public interface IBase
    {
        void Do();
    }

    public interface IDerived1 : IBase
    {
        new void Do();
    }

    public interface IDerived2 : IBase
    {
        new void Do();
    }

}
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Отредактировано 31.07.2020 8:40 igor-booch . Предыдущая версия . Еще …
Отредактировано 31.07.2020 8:38 igor-booch . Предыдущая версия .
Re[2]: Явная реализация интерфейса и generic метод
От: igor-booch Россия  
Дата: 31.07.20 08:27
Оценка:
S>попробуй
S> public static void Do<TClass>(TClass @object) where TClass : IBase
S> {
S>switch(@object)
S>{
S> case IDerived2 d2:@d2.Do();break;
S> case IDerived1 d1: d1.Do();break;
S> default @object.Do();break;
S> }


Не рабоатет

IBase
IDerived1
IDerived2
IDerived2
IDerived2
IDerived2

Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Re[2]: Явная реализация интерфейса и generic метод
От: igor-booch Россия  
Дата: 31.07.20 08:31
Оценка:
S>Может, вам dynamic попробовать?

Ума не приложу как тут dynamic впихнуть
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Re[3]: Явная реализация интерфейса и generic метод
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 31.07.20 08:41
Оценка: 4 (1)
Здравствуйте, igor-booch, Вы писали:

S>>попробуй

S>> public static void Do<TClass>(TClass @object) where TClass : IBase
S>> {
S>>switch(@object)
S>>{
S>> case IDerived2 d2:@d2.Do();break;
S>> case IDerived1 d1: d1.Do();break;
S>> default @object.Do();break;
S>> }


IB>Не рабоатет

IB>

IB>IBase
IB>IDerived1
IB>IDerived2
IB>IDerived2
IB>IDerived2
IB>IDerived2


А ну да нужно сравнивать тип
if (typeof(TClass)==typeof(IDerived2))
{
(object as IDerived2).Do();
}
else if (typeof(TClass)==typeof(IDerived1))
{
(object as IDerived1).Do();
}
и солнце б утром не вставало, когда бы не было меня
Отредактировано 31.07.2020 14:25 Serginio1 . Предыдущая версия .
Re[3]: Явная реализация интерфейса и generic метод
От: Sinclair Россия https://github.com/evilguest/
Дата: 31.07.20 14:12
Оценка:
Здравствуйте, igor-booch, Вы писали:

S>>Может, вам dynamic попробовать?


IB>Ума не приложу как тут dynamic впихнуть

Я сгоряча подумал про
  public static void Do<TClass>(TClass @object) where TClass : IBase
  {
    dynamic d = @object;
    d.Do();
  }

Но так не работает: class ConsoleApp2.Class doesn't have a method named Do.
Потому что явные реализации — не паблик.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Сделал так
От: Sinclair Россия https://github.com/evilguest/
Дата: 31.07.20 14:19
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>Всем спасибо, c рефлешеном, понятно, что можно всё, но производительность страдает,

Можно сделать так:
    public static class Doer
    {
        private static Dictionary<Type, MethodInfo> _methodCache = new Dictionary<Type, MethodInfo>();
        public static void Do<I>(I target) where I : IBase
        {
            MethodInfo m;
            if (!_methodCache.TryGetValue(typeof(I), out m))
            {
                m = typeof(I).GetMethod("Do");
                _methodCache[typeof(I)] = m;
            }
            m.Invoke(target, null);
        }
    }
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: Явная реализация интерфейса и generic метод
От: artelk  
Дата: 31.07.20 14:38
Оценка: 4 (1)
Здравствуйте, igor-booch, Вы писали:

IB>Есть ли способ сделать так, чтобы вывод был:


IB>

IB>IBase
IB>IDerived1
IB>IDerived2
IB>IBase
IB>IDerived1
IB>IDerived2


IB>???


Еще вариант, с компиляцией lambda expression на первом вызове.

class Program
{
    static void Main(string[] args)
    {
        Class @object = new Class();
        IBase @base = @object;
        IDerived1 derived1 = @object;
        IDerived2 derived2 = @object;

        @base.Do();
        derived1.Do();
        derived2.Do();

        Do<IBase>(@base);
        Do<IDerived1>(derived1);
        Do<IDerived2>(derived2);

        Console.ReadLine();
    }

    private static class DoMethod<T>
        where T : IBase
    {
        public static readonly Action<T> Call;

        static DoMethod()
        {
            var doMethod = typeof(T).GetMethod("Do"); //TODO: handle corner cases
            var a = Expression.Parameter(typeof(T), "a");
            Call = Expression.Lambda<Action<T>>(Expression.Call(a, doMethod), a).Compile();
        }
    }

    public static void Do<TClass>(TClass @object) where TClass : IBase
    {
        DoMethod<TClass>.Call(@object);
    }
}
Re[2]: Явная реализация интерфейса и generic метод
От: samius Япония http://sams-tricks.blogspot.com
Дата: 31.07.20 17:35
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, igor-booch, Вы писали:

S>Может, вам dynamic попробовать?

Тут случай, когда класс реализует более одного интерфейса, по которым нужна диспетчеризация. Динамик вынесет с заклом RuntimeBinderException.
Начал прикручивать через ad-hoc и обнаружил такое...
Re[2]: Сделал так
От: Mystic Artifact  
Дата: 31.07.20 19:26
Оценка: 86 (3)
Здравствуйте, igor-booch, Вы писали:

IB>Всем спасибо, c рефлешеном, понятно, что можно всё, но производительность страдает,

IB>пока сделал так

С точки зрения производительности, не смотря, что такой паттерн не очень красивый (и имеет свои ограничения), — похожие паттерны уже распознаются JIT.

Ну, например:

public T GetValue<T>() {
  if (typeof(T) == typeof(int)) {
    return (T)(object)GetIntValue();
  }
  else if (typeof(T) == typeof(double)) {
    return (T)(object)GetDoubleValue();
  }
  else if (typeof(T) == typeof(string)) {
    return (T)(object)GetStringValue();
  }
  else throw new InvalidOperationException();
}


Такой метод будет раскрываться в GetIntValue/GetDoubleValue/GetStringValue как будто никакого generic метода и нет вовсе, и т.к. T заранее известен — цепочка боксинга/анбоксинга так же подавляется, даже в Debug билдах.

--

С методом "public static void Do<TClass>(TClass @object) where TClass : IBase" немного иначе — в виду того, что на сегодня есть только фич-реквесты для раздельной компиляции специализаций. Тем не менее если метод Do<TClass> заставить инлайнится — то результат будет так же хорошим.

В частности с хинтом AggressiveInlining на Do и AggressiveOptimization на Main (не уверен что они нужны), метод Main примет форму (испытано на .net 5):

IN0015: 000000 sub      rsp, 40

G_M59663_IG02:        ; offs=000004H, size=0072H, bbWeight=1    PerfScore 20.75, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref

IN0001: 000004 mov      rcx, 0x257900030B8
IN0002: 00000E mov      rcx, gword ptr [rcx]
IN0003: 000011 call     Console:WriteLine(String)
IN0004: 000016 mov      rcx, 0x257900030C0
IN0005: 000020 mov      rcx, gword ptr [rcx]
IN0006: 000023 call     Console:WriteLine(String)
IN0007: 000028 mov      rcx, 0x257900030C8
IN0008: 000032 mov      rcx, gword ptr [rcx]
IN0009: 000035 call     Console:WriteLine(String)
IN000a: 00003A mov      rcx, 0x257900030B8
IN000b: 000044 mov      rcx, gword ptr [rcx]
IN000c: 000047 call     Console:WriteLine(String)
IN000d: 00004C mov      rcx, 0x257900030C0
IN000e: 000056 mov      rcx, gword ptr [rcx]
IN000f: 000059 call     Console:WriteLine(String)
IN0010: 00005E mov      rcx, 0x257900030C8
IN0011: 000068 mov      rcx, gword ptr [rcx]
IN0012: 00006B call     Console:WriteLine(String)
IN0013: 000070 call     Console:ReadLine():String
IN0014: 000075 nop      

G_M59663_IG03:        ; offs=000076H, size=0005H, bbWeight=1    PerfScore 1.25, epilog, nogc, extend

IN0016: 000076 add      rsp, 40
IN0017: 00007A ret


Как видно — в данном простом случае — JIT справился более чем отлично. Понятно, что это не совсем универсально (в случае если JIT откажется инлайнить — то будет уже совсем не всё так красиво ).

PS: Я имел ввиду, что с точки зрения производительности — на сегодня это один из самых простых и многообещающих способов. С другой стороны рефлексия + кеширование имеет стабильно предсказуемый результат в отличии от закидонов JIT.
Отредактировано 31.07.2020 19:33 Mystic Artifact . Предыдущая версия .
Re[3]: Сделал так
От: igor-booch Россия  
Дата: 01.08.20 17:33
Оценка:
MA>В частности с хинтом AggressiveInlining на Do и AggressiveOptimization на Main (не уверен что они нужны), метод Main примет форму (испытано на .net 5)

Offtop
У меня есть много методов, которые я хотел бы заинлайнить для повышения производительности.
Можете ли Вы посоветовать ставить для них атрибуты AggressiveInlining и AggressiveOptimization?
Или пользоваться этими атрибутами нужно осторожно, со знанием всех тонкостей?
Что кроме атрибутов AggressiveInlining и AggressiveOptimization нужно для инлайнинга методов?
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Отредактировано 01.08.2020 17:38 igor-booch . Предыдущая версия .
Re[4]: Сделал так
От: Mystic Artifact  
Дата: 01.08.20 18:42
Оценка: 6 (1)
Здравствуйте, igor-booch, Вы писали:

IB>Offtop

IB>У меня есть много методов, которые я хотел бы заинлайнить для повышения производительности.
IB>Можете ли Вы посоветовать ставить для них атрибуты AggressiveInlining и AggressiveOptimization?
IB>Или пользоваться этими атрибутами нужно осторожно, со знанием всех тонкостей?
IB>Что кроме атрибутов AggressiveInlining и AggressiveOptimization нужно для инлайнинга методов?

Можно на ты. В целом это зависит от рантайма. Тут прежде всего важно — что это просто хинты — и рантайм в праве не следовать им, если не считает нужным.

Простейшие случаи можно проверить на sharplab.io. Случаи посложнее — если иниересует .net core — прийдется сбилдить рантайм (это просто). Инструкция Viewing jit dumps. Единственно, что там инструкции не очень точны. Проще всего собрать только отладочную версию и заменять только clrjit.dll. При этом важно, что бы собранная версия соответствовала рантайму который используется при публикации. С тэгами они ессно в репозитории все напутали, таким образом я не нашел тэга для 3.1 а взял соотв. тэг от проинсталлированного 5.0-preview6 (ну или какойтам инсталлируется). Если версии ре совпадают сильно — оно просто крэшнется и все. При чем именно их способ по инструкции у мкня вообще никак не заработал. Второе — уделить внимание переменным COMPlus_*. Так как оно работало раньше — у меня не работает, нашел новые опции — писать вывод в отдельный файл и все получилось. Сейчас не у компа, не знаю точнее. Позже могу уточнить.

Ну а после этого можно получать много информации. Там в частности есть и про инлайнинг опция — оно приблизительно сообщает почему инлайнит или почему нет. (Опять же tiered compilation — враг инлайнинга, — в том смысле что для таких целей проще отклють, чем ждать пока случится tier 1). Возможно есть и иные способы, я не знаю...

Что касается о расстановке атрибутов — но в 80% случаев они не нужны.

Однако, если мы говорим о нормальном фреймворке — 4.х — то там я перестал следить. 4.5 был печальным. А 4.8 — я честно говоря х.з. Сейчас в современных часто трюки с выносом throw в отдельные методы могут и не понадобится.

Иначе говоря: старайтесь не использовать эти атрибуты вообще. В реальном коде, они чаще всего никакого значения не имеют. Там где это важно — нужно проверять как JIT отработает с ним или без. Есть ситуации когда мы точно знаем что надо инлайнить — но, часто это обычные форварды вызовов где JIT и сам могуч. Поэтому по сути — рекомендаций, к сожалению не дам. Это экспериментальная часть.

К слову: у меня в коде применение BinaryPrimitives+Span дало 30% выигрыша. Но! Эта кухня в дебаге потеряла в скорости в 20х раз.
Re[5]: Сделал так
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 02.08.20 05:26
Оценка:
Здравствуйте, Mystic Artifact, Вы писали:

MA> К слову: у меня в коде применение BinaryPrimitives+Span дало 30% выигрыша. Но! Эта кухня в дебаге потеряла в скорости в 20х раз.

Т.е. так конкретно тормозит отладчик, или просто стало очень сложно отлаживать?
Re[6]: Сделал так
От: Mystic Artifact  
Дата: 02.08.20 17:12
Оценка:
Здравствуйте, Михаил Романов, Вы писали:

MA>> К слову: у меня в коде применение BinaryPrimitives+Span дало 30% выигрыша. Но! Эта кухня в дебаге потеряла в скорости в 20х раз.

МР>Т.е. так конкретно тормозит отладчик, или просто стало очень сложно отлаживать?

Нет, просто оригинальная конструкция была приблизительно такая (псевдо код):

return _data[_offset] + _data[_offset + 1] << 8 + _data[_offset + 2] << 16 + _data[_offset + 3] << 24;


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

Заменив на BinaryPrimitives + Span я считай заменил простой код на 2 вызова метода (конструктор Span + сама "конвертация"), и в отсутствии оптимизаций оно таким и остается и буксует.
Re[7]: Сделал так
От: Sinclair Россия https://github.com/evilguest/
Дата: 03.08.20 06:10
Оценка: 8 (1) +1
Здравствуйте, Mystic Artifact, Вы писали:

MA> Заменив на BinaryPrimitives + Span я считай заменил простой код на 2 вызова метода (конструктор Span + сама "конвертация"), и в отсутствии оптимизаций оно таким и остается и буксует.

Эмм, а тут случайно не напрашивается
MemoryMarshal.Cast<byte, uint>(_data)

?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[8]: Сделал так
От: Mystic Artifact  
Дата: 03.08.20 09:21
Оценка:
Здравствуйте, Sinclair, Вы писали:

Напрашивается. Там напрашивается вообще сразу выделять int[], но есть пара int16 полей. Я думал об этом, да руки не дошли, и местами удобнее/привычнее работать с массивом байт... А все эти причудливые трансформации реально JIT сильно упрощает, так что выигрыша можно и не получить. Надо будет поковырять.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.