Обобщения в c # и доступ к статическим членам T - PullRequest
13 голосов
/ 21 августа 2008

Мой вопрос касается c # и как получить доступ к 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()
     }
}

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

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


Редактировать: Спасибо всем за ответы!

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

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


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

Ответы [ 12 ]

5 голосов
/ 21 августа 2008

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

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

3 голосов
/ 21 августа 2008

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

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

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 августа 2008

Чтобы получить доступ к члену определенного класса или интерфейса, вам нужно использовать ключевое слово 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;
       }
    }
}
2 голосов
/ 21 августа 2008

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

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 );

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

1 голос
/ 15 мая 2012

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

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

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

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 голос
/ 21 августа 2008

Единственный способ сделать именно то, что вы ищете, это использовать отражение, чтобы проверить, существует ли метод для 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 августа 2008

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

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

0 голосов
/ 16 июля 2009

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

class test1<T> where T: struct

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

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

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

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

SL.

0 голосов
/ 21 августа 2008

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

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

Если вы хотите, чтобы я объяснил что-либо из перечисленного выше, спросите:)

...