Есть ли обходной путь для использования статических методов в универсальном классе?

У меня довольно простая проблема, но, похоже, в C # нет решения.

У меня около 100 Foo классов, каждый из которых реализует static FromBytes() метод. Есть также несколько общих классов, которые должны использовать эти методы для своих собственных FromBytes(). НО универсальные классы не могут использовать static FromBytes() методы, потому что T.FromBytes(...) является недопустимым.

Я что-то упускаю или нет возможности реализовать эту функцию?

public class Foo1
{
    public static Foo1 FromBytes(byte[] bytes, ref int index)
    {
        // build Foo1 instance
        return new Foo1()
        {
            Property1 = bytes[index++],
            Property2 = bytes[index++],
            // [...]
            Property10 = bytes[index++]
        };
    }

    public int Property1 { get; set; }
    public int Property2 { get; set; }
    // [...]
    public int Property10 { get; set; }
}

//public class Foo2 { ... }
// [...]
//public class Foo100 { ... }

// Generic class which needs the static method of T to work
public class ListOfFoo<T> : System.Collections.Generic.List<T>
{
    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        var count = bytes[index++];
        var listOfFoo = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            listOfFoo.Add(T.FromBytes(bytes, ref index)); // T.FromBytes(...) is illegal
        }

        return listOfFoo;
    }
}

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


person Karsten Gutjahr    schedule 11.11.2013    source источник
comment
Попробуйте метод расширения.   -  person Hossain Muctadir    schedule 11.11.2013
comment
Привет, @Muctadir, к сожалению, у меня те же проблемы в универсальном методе расширения, что и в универсальном классе.   -  person Karsten Gutjahr    schedule 11.11.2013
comment
Как вы вызываете статический метод ListOfFoo<T>.FromBytes(...)? Вам все равно придется указать аргумент типа, не так ли? Например: ListOfFoo<Foo1>.FromBytes(...). Затем вы можете передать делегата в качестве дополнительного параметра (например, Предлагаемое обслуживание). Или вы называете это отражением?   -  person gehho    schedule 11.11.2013


Ответы (8)


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

public delegate T FromBytesFunc<T>(byte[] bytes, ref int index);
public static List<T> FromBytes<T>(byte[] bytes, ref int index,
    FromBytesFunc<T> function)
{
    var count = bytes[index++];
    var listOfFoo = new List<T>();
    for (var i = 0; i < count; i++)
    {
        listOfFoo.Add(function(bytes, ref index));
    }

    return listOfFoo;
}

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

var list = SomeClass.FromBytes(bytes, ref index, Foo1.FromBytes);
person Servy    schedule 11.11.2013

Проблема в том, что вы пытаетесь использовать FromBytes в качестве метода расширения, когда это не так. Насколько я понимаю, вы пытаетесь вызвать соответствующий FromBytes для того, что есть <T>, как определено в любом классе, который вы уже создали для T.

Что вам нужно сделать, так это вызвать метод через отражение.

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

Используйте Reflection для вызова универсального метода для объекта экземпляр с подписью: SomeObject.SomeGenericInstanceMethod ‹T› (аргумент T)

Как использовать отражение для вызова универсального метода?

Вызов универсального метода с использованием отражения в .NET

Как вызвать универсальный метод с заданным объектом типа? < / а>

person Smeegs    schedule 11.11.2013
comment
Если бы я использовал отражение для определения типа, я бы мог вызвать ´Foo42.FromBytes () ´. Но я не могу добавить получившийся экземпляр в список: ´listOf.Add (Foo42.FromBytes (Bytes, ref index)); ´ дает мне Тип аргумента «Foo42» не может быть назначен типу параметра T .. - person Karsten Gutjahr; 11.11.2013
comment
Хммм, а результат кастом пробовал? (T)Foo42.FromBytes(Bytes, ref index)? - person Smeegs; 11.11.2013
comment
Хммм, извини, я ничем не мог больше помочь. Но я не уверен. Возможно, попробуйте перепостить этот вопрос, но с отражением в заголовке. Вы можете привлечь гуру рефлексии. - person Smeegs; 11.11.2013
comment
Попробуйте выполнить трансляцию в object, а затем в T: (T)((object)Foo42.FromBytes(Bytes, ref index)) - person gehho; 11.11.2013

Не могли бы вы просто реализовать статический метод FromBytes в общем виде в служебном классе? Тогда сделать что-нибудь подобное?

listOf.Add(Utility.FromBytes<T>(bytes, ref index));

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

Что-то вроде этого:

public static class Utility
{
    public static T FromBytes<T>(byte[] bytes, ref int index)
    {
          if (typeof(T) is Foo1)
          {
               return Foo1.GetBytes(bytes, ref index);
          }
          //etc....
    }
}

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

person Kevin DiTraglia    schedule 11.11.2013
comment
Благодарим за проявленный интерес, но интерфейсы не могут иметь статических методов. Также абстрактные классы не могут иметь ничего статичного. - person Karsten Gutjahr; 11.11.2013
comment
Думаю, тебе нужно полагаться на размышления. - person Wagner DosAnjos; 11.11.2013
comment
Интересно, что на самом деле я этого не знал. Думаю, размышления - вот способ сделать это. - person Kevin DiTraglia; 11.11.2013
comment
Это просто меняет вопрос на: как реализовать Utility.FromBytes? - person svick; 11.11.2013
comment
@svick Я бы вообразил гигантский оператор case, который отражает текущее определение каждого класса (или просто вызывает те классы, которые уже сделали определение). - person Kevin DiTraglia; 11.11.2013
comment
К сожалению, я не могу вернуть Foo42, если должен вернуть T. Кастинг кажется невозможным. - person Karsten Gutjahr; 11.11.2013
comment
@KarstenGutjahr Хммм, это кажется сложнее, чем должно быть. Я полагаю, вы могли бы вернуть объект вместо T в качестве временного решения. Похоже, что должно существовать что-то более элегантное. - person Kevin DiTraglia; 11.11.2013
comment
@KarstenGutjahr Cast возможно. Требуется немного поработать. Дорого для типов значений. и вот как вы идете. [return (T) (object) someType] - person Sriram Sakthivel; 11.11.2013

Вы можете использовать отражение, чтобы найти метод для этого типа, а затем создать для него делегат. Что-то типа:

delegate T FromBytesFunc<T>(byte[] bytes, ref int index);

public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
{
    FromBytesFunc<T> fromBytes =
        (FromBytesFunc<T>)Delegate.CreateDelegate(
            typeof(FromBytesFunc<T>), typeof(T).GetMethod("FromBytes")

    var count = bytes[index++];
    var listOf = new ListOfFoo<T>();
    for (var i = 0; i < count; i++)
    {
        listOf.Add(fromBytes(bytes, ref index));
    }

    return listOf;
}
person svick    schedule 11.11.2013
comment
Это выглядит интересно, но в «MethodInfo» нет метода «CreateDelegate ()». ;-) - person Karsten Gutjahr; 11.11.2013
comment
@KarstenGutjahr Есть, но только в .Net 4.5, я этого не заметил. - person svick; 11.11.2013

Вы можете попробовать что-то вроде этого:

sealed class RetType
{
    public object Value
    {
        get;
        private set;
    }

    public int Index
    {
        get;
        private set;
    }

    public RetType(object value, int index)
    {
        Value = value;
        Index = index;
    }
}

public class ListOfFoo<T> : System.Collections.Generic.List<T>
{
    static readonly Dictionary<Type, Func<byte[], int, RetType>> dic = new Dictionary<Type, Func<byte[], int, RetType>>
    {
        {
            typeof(Foo1),
            new Func<byte[], int, RetType>((bytes, index) =>
            {
                var value = Foo1.FromBytes(bytes, ref index);

                return new RetType(value, index);
            })
        }
        // add here others Foo
    };

    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        var count = bytes[index++];
        var listOf = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            var o = dic[typeof(T)](bytes, index);

            listOf.Add((T)o.Value);

            index = o.Index;
        }

        return listOf;
    }
}

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

person Alessandro D'Andria    schedule 11.11.2013
comment
То есть вы говорите, что словарь нужно заполнять вручную для каждого Foo? Это не похоже на хорошее решение. - person svick; 11.11.2013
comment
Очевидно, и я согласен, что это не очень хорошее решение, но это решение. Другой очевидный вариант - рефлексия, предложенная выше. - person Alessandro D'Andria; 11.11.2013
comment
Меня также интересуют не очень хорошие решения, но для компилятора «T» в «RetType ‹T›» не совпадает с «T», как в «ListOfFoo ‹T›», просто имеет одно и то же имя. Поэтому компилятор не может преобразовать одно в другое ... :-( - person Karsten Gutjahr; 11.11.2013
comment
@KarstenGutjahr хорошо, я переработал код и протестировал его, посмотрите. - person Alessandro D'Andria; 11.11.2013
comment
Хорошо, это кажется разумным. Я просто боюсь, что этот код может быть сокращен до (T)((object)Foo42.FromBytes(Bytes, ref index)), который был прокомментирован здесь через 20 минут после вашего комментария. - person Karsten Gutjahr; 11.11.2013
comment
Я уверен, что наполнение Словаря можно автоматизировать: foreach (var type in Assembly.GetExecutingAssembly().GetTypes().Where(t => t.Namespace == "Foos")) { Dict.Add(type, (FromBytesFunc<T>)Delegate.CreateDelegate(typeof(FromBytesFunc<T>), type.GetMethod("FromBytes"))); } с private delegate T2 FromBytesFunc<out T2>(byte[] bytes, ref int index); Но тогда у вас нет проверки компилятором. - person Karsten Gutjahr; 12.11.2013

Не уверен, работает ли это для вас, но это вариант:

  1. Определите интерфейс IHaveFromBytesMethod<T>, который определяет метод экземпляра FromBytes(...).
  2. Сделайте так, чтобы все Foo# типы реализовали этот интерфейс. (Если вы не хотите, чтобы этот метод экземпляра был виден немедленно, вы можете явно реализовать интерфейс.)
  3. Все реализации метода экземпляра просто перенаправляют вызов статическому методу этого типа.
  4. Ограничьте параметр типа T в своем классе ListOfFoo<T>, чтобы реализовать этот интерфейс и предоставить конструктор без параметров.
  5. Если вам нужен статический метод, создайте новый фиктивный объект типа T с помощью конструктора без параметров, а затем вызовите метод экземпляра для этого фиктивного экземпляра.

Интерфейс:

public interface IHaveFromBytesMethod<T>
{
    T FromBytes(byte[] bytes, ref int index);
}

Один из Foo# классов:

public class Foo1 : IHaveFromBytesMethod<Foo1>
{
    public Foo1()
    {
        // ...
    }

    public static Foo1 FromBytes(byte[] bytes, ref int index)
    {
        // ...
    }

    public Foo1 FromBytes(byte[] bytes, ref int index)
    {
        // within the instance method simply call the static method
        return Foo1.FromBytes(bytes, ref index);
    }
}

Модифицированный ListOfFoo<T> класс:

// requires T to implement the interface and provide a parameterless ctor!
public class ListOfFoo<T> : System.Collections.Generic.List<T>
    where T : IHaveFromBytesMethod<T>, new()
{
    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        // create dummy instance for accessing static method via instance method
        T dummy = new T();
        var count = bytes[index++];
        var listOfFoo = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            // instead of calling the static method,
            // call the instance method on the dummy instance
            listOfFoo.Add(dummy.FromBytes(bytes, ref index));
        }

        return listOfFoo;
    }
}
person gehho    schedule 11.11.2013
comment
Это действительно уродливо. По логике вещей создавать экземпляр Foo для создания другого экземпляра не имеет смысла. - person svick; 11.11.2013
comment
Это так. Но не все ли решения на этой странице в чем-то уродливы? Я просто хотел предложить альтернативу, которая работает без размышлений. - person gehho; 11.11.2013

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

public class ListOfFoo<T> : System.Collections.Generic.List<T>
{
    private static readonly FromBytesFunc<T> BytesFromFunc = 
        (FromBytesFunc<T>)System.Delegate.CreateDelegate(
            typeof(FromBytesFunc<T>),
            typeof(T).GetMethod("FromBytes"));

    private delegate T2 FromBytesFunc<out T2>(byte[] bytes, ref int index);

    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        var count = bytes[index++];
        var listOfFoo = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            listOfFoo.Add(BytesFromFunc(bytes, ref index));
        }

        return listOfFoo;
    }
}
person Karsten Gutjahr    schedule 12.11.2013

Это будет итог для всех будущих зрителей.

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

  • через отражение
  • от звонящего

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

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

PS: Некоторые люди предложили иметь словарь или переключатель / case или if / else, где хранятся делегаты. Этого делать не следует. Это не имеет преимуществ перед хранением делегата в статическом поле универсального класса (универсальные типы не имеют общих статических членов).

person Karsten Gutjahr    schedule 28.03.2014