Подтвердить что ты не робот

Дженерики в С# и доступ к статическим членам T

Мой вопрос касается С# и того, как получить доступ к Static memebers... Ну, я действительно не знаю, как это объяснить (какой-то плохой вопрос, не так ли?) Я просто дам вам пример кода

Class test<T>{
     int method1(Obj Parameter1){
         //in here I want to do something which I would explain as
         T.TryParse(Parameter1);

         //my problem is that it does not work ... I get an error.
         //just to explain: if I declare test<int> (with type Integer)
         //I want my sample code to call int.TryParse(). If it were String
         //it should have been String.TryParse()
     }
}

Так спасибо вам, ребята, за ваши ответы (Кстати, вопрос: как я могу решить эту проблему, не получив ошибку). Это, вероятно, довольно простой вопрос для вас!

Спасибо, Никлас


Изменить: Спасибо всем за ваши ответы!

Хотя я думаю, что фраза try-catch является самой элегантной, из моего опыта работы с vb я знаю, что это действительно может быть облом. Я использовал его один раз, и для запуска программы потребовалось около 30 минут, а позже потребовалось всего 2 минуты, чтобы вычислить только потому, что я избегал try-catch.

Вот почему я выбрал предложение swich как лучший ответ. Это делает код более сложным, но, с другой стороны, я считаю, что он относительно быстро и относительно легко читается. (Хотя я все еще думаю, что должен быть более элегантный способ... может быть, на следующем языке я узнаю: P)


Хотя, если у вас есть какое-то другое предложение, я все еще жду (и желаю принять участие)

4b9b3361

Ответ 1

Еще один способ сделать это, на этот раз некоторое отражение в миксе:

static class Parser
{
    public static bool TryParse<TType>( string str, out TType x )
    {
        // Get the type on that TryParse shall be called
        Type objType = typeof( TType );

        // Enumerate the methods of TType
        foreach( MethodInfo mi in objType.GetMethods() )
        {
            if( mi.Name == "TryParse" )
            {
                // We found a TryParse method, check for the 2-parameter-signature
                ParameterInfo[] pi = mi.GetParameters();
                if( pi.Length == 2 ) // Find TryParse( String, TType )
                {
                    // Build a parameter list for the call
                    object[] paramList = new object[2] { str, default( TType ) };

                    // Invoke the static method
                    object ret = objType.InvokeMember( "TryParse", BindingFlags.InvokeMethod, null, null, paramList );

                    // Get the output value from the parameter list
                    x = (TType)paramList[1];
                    return (bool)ret;
                }
            }
        }

        // Maybe we should throw an exception here, because we were unable to find the TryParse
        // method; this is not just a unable-to-parse error.

        x = default( TType );
        return false;
    }
}

Следующий шаг будет пытаться реализовать

public static TRet CallStaticMethod<TRet>( object obj, string methodName, params object[] args );

С полным сопоставлением типов параметров и т.д.

Ответ 2

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

Ограничения по параметрам типа

Ответ 3

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

В приведенном выше примере TryParse не приходит из интерфейса или базового класса, поэтому то, что вы пытаетесь сделать выше, невозможно. Лучше всего использовать Convert.ChangeType и оператор try/catch.

class test<T>
{
    T Method(object P)
    {
       try {
           return (T)Convert.ChangeType(P, typeof(T));
       } catch(Exception e) {
           return null;
       }
    }
}

Ответ 4

Короткий ответ, вы не можете.

Длинный ответ, вы можете обмануть:

public class Example
{
    internal static class Support
    {
        private delegate bool GenericParser<T>(string s, out T o);
        private static Dictionary<Type, object> parsers =
            MakeStandardParsers();
        private static Dictionary<Type, object> MakeStandardParsers()
        {
            Dictionary<Type, object> d = new Dictionary<Type, object>();
            // You need to add an entry for every type you want to cope with.
            d[typeof(int)] = new GenericParser<int>(int.TryParse);
            d[typeof(long)] = new GenericParser<long>(long.TryParse);
            d[typeof(float)] = new GenericParser<float>(float.TryParse);
            return d;
        }
        public static bool TryParse<T>(string s, out T result)
        {
            return ((GenericParser<T>)parsers[typeof(T)])(s, out result);
        }
    }
    public class Test<T>
    {
        public static T method1(string s)
        {
            T value;
            bool success = Support.TryParse(s, out value);
            return value;
        }
    }
    public static void Main()
    {
        Console.WriteLine(Test<int>.method1("23"));
        Console.WriteLine(Test<float>.method1("23.4"));
        Console.WriteLine(Test<long>.method1("99999999999999"));
        Console.ReadLine();
    }
}

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

Ответ 5

Хорошо, ребята: Спасибо за всю рыбу. Теперь с вашими ответами и моими исследованиями (особенно статья ограничение общих типов примитивами). Я представлю вам мое решение.

Class a<T>{
    private void checkWetherTypeIsOK()
    {
        if (T is int || T is float //|| ... any other types you want to be allowed){
            return true;
        }
        else {
            throw new exception();
        }
    }
    public static a(){
        ccheckWetherTypeIsOK();
    }
}

Ответ 6

Вы хотите сделать что-то вроде этого:

Class test<T>
{
     T method1(object Parameter1){

         if( Parameter1 is T ) 
         {
              T value = (T) Parameter1;
             //do something with value
             return value;
         }
         else
         {
             //Parameter1 is not a T
             return default(T); //or throw exception
         }
     }
}

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

Ответ 7

Единственный способ сделать то, что вы ищете, - это использовать отражение, чтобы проверить, существует ли метод для T.

Другая опция - гарантировать, что объект, который вы отправляете, является конвертируемым объектом, сдерживая тип IConvertible (все примитивные типы реализуют IConvertible). Это позволит вам очень гибко преобразовать ваш параметр в заданный тип.

Class test<T>
{
    int method1(IConvertible Parameter1){

        IFormatProvider provider = System.Globalization.CultureInfo.CurrentCulture.GetFormat(typeof(T));

        T temp = Parameter1.ToType(typeof(T), provider);
    }
}

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

Class test<T>
{
    int method1(object Parameter1){

        if(Parameter1 is IConvertible) {

            IFormatProvider provider = System.Globalization.CultureInfo.CurrentCulture.GetFormat(typeof(T));

            T temp = Parameter1.ToType(typeof(T), provider);

        } else {
           // Do something else
        }
    }
}

Ответ 8

На самом деле это не решение, но в некоторых сценариях это может быть хорошей альтернативой: мы можем передать дополнительный делегат в общий метод.

Чтобы уточнить, что я имею в виду, позвольте использовать пример. Скажем, у нас есть общий метод factory, который должен создать экземпляр T, и мы хотим, чтобы он затем вызывал другой метод для уведомления или дополнительной инициализации.

Рассмотрим следующий простой класс:

public class Example
{
    // ...

    public static void PostInitCallback(Example example)
    {
        // Do something with the object...
    }
}

И следующий статический метод:

public static T CreateAndInit<T>() where T : new()
{
    var t = new T();
    // Some initialization code...
    return t;
}

Итак, сейчас нам нужно будет сделать:

var example = CreateAndInit<Example>();
Example.PostInitCallback(example);

Однако мы могли бы изменить наш метод, чтобы взять дополнительный делегат:

public delegate void PostInitCallback<T>(T t);
public static T CreateAndInit<T>(PostInitCallback<T> callback) where T : new()
{
    var t = new T();
    // Some initialization code...
    callback(t);
    return t;
}

И теперь мы можем изменить вызов:

var example = CreateAndInit<Example>(Example.PostInitCallback);

Очевидно, что это полезно только в очень специфических сценариях. Но это самое чистое решение в том смысле, что мы получаем время компиляции, нет "взлома", и код прост.

Ответ 9

Возможно, вы не можете это сделать.

Прежде всего, если это будет возможно, вам понадобится более жесткая привязка к T, поэтому typechecker может быть уверен, что все возможные подстановки для T на самом деле имеют статический метод TryParse.

Ответ 10

Возможно, вы захотите прочитать мой предыдущий пост ограничивающий общие типы примитивами. Это может дать вам некоторые указатели на ограничение типа, который может быть передан в общий (поскольку TypeParse, очевидно, доступен только для набора числа примитивов (string.TryParse, очевидно, является исключением, что не имеет смысла).

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

Если вам нужно, чтобы я объяснил все вышеперечисленное, пожалуйста, спросите:)

Ответ 11

Лучший код: ограничьте T значением ValueType следующим образом:

class test1<T> where T: struct

A "struct" здесь означает тип значения. String - это класс, а не тип значения. int, float, Enums - все типы значений.

btw компилятор не принимает вызов статических методов или получает доступ к статическим членам в параметрах типа, как в следующем примере, который не будет компилироваться: (

class MyStatic { public static int MyValue=0; }
class Test<T> where T: MyStatic
{
    public void TheTest() { T.MyValue++; }
}

= > Ошибка 1 'T' - это параметр типа, который недопустим в данном контексте

SL.

Ответ 12

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

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

class a {
    static StaticMethod1 ()
    virtual Method1 ()
}

class b : a {
    override Method1 () return StaticMethod1()
}

class c : a {
    override Method1 () return "XYZ"
}

class generic<T> 
    where T : a {
    void DoSomething () T.Method1()
}