Обобщения в c # и доступ к статическим членам T

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

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()
     }
}

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

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


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

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

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


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

21.08.2008 13:20:05
12 ОТВЕТОВ
РЕШЕНИЕ

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

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
21.08.2008 14:46:46
Это безбожно медленно. Вы должны кэшировать общий делегат.
SLaks 3.01.2011 16:13:24

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

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

5
21.08.2008 13:28:29

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

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

0
21.08.2008 13:28:49

Это не то, как работает статика. Вы должны думать о статиках как о классе Global, даже если они распределены по целому ряду типов. Я рекомендую сделать это свойство внутри экземпляра 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()
}
-1
21.08.2008 13:30:18

Чтобы получить доступ к члену определенного класса или интерфейса, вам нужно использовать ключевое слово 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;
       }
    }
}
3
25.06.2014 11:42:34

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

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

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

0
23.05.2017 12:24:55

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

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, так как он статический, что, к сожалению, означает, что он не особенно подходит для генериков.

1
21.08.2008 13:39:02

Единственный способ сделать именно то, что вы ищете, это использовать отражение, чтобы проверить, существует ли метод для 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
        }
    }
}
1
21.08.2008 14:01:57

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

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

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 каждого типа, который я мог бы использовать. Затем я написал общий метод для поиска в словаре и передачи вызова соответствующему делегату. Поскольку каждый делегат имеет свой тип, я просто сохраняю их как ссылки на объекты и возвращаю их к соответствующему универсальному типу, когда получаю их. Обратите внимание, что в качестве простого примера я пропустил проверку ошибок, например, чтобы проверить, есть ли у нас запись в словаре для данного типа.

3
21.08.2008 14:08:12

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

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();
    }
}
1
23.05.2017 12:33:45

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

class test1<T> where T: struct

«Структура» здесь означает тип значения. Строка - это класс, а не тип значения. int, float, Enums - все типы значений.

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

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

=> Ошибка 1 «T» является «параметром типа», который недопустим в данном контексте

Уровень моря

0
16.07.2009 16:20:15

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

Чтобы уточнить, что я имею в виду, давайте использовать пример. Допустим, у нас есть некоторый универсальный фабричный метод, который должен создать экземпляр 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);

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

1
14.05.2012 20:34:24