2008-08-21 15 views
12

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

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

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

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


Edit: Спасибо всем за ответы!

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

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


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

ответ

2

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

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

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

+0

Это не очень медленно. Вы должны кэшировать общий делегат. – SLaks 2011-01-03 16:13:24

5

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

Constraints on Type Parameters

0

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

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

-1

Это не так, как статична работа. Вы должны думать о статике как о классе в глобальном классе, даже если они распределены по целому ряду типов. Моя рекомендация - сделать его собственностью внутри экземпляра 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() 
} 
3

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

В приведенном выше примере 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; 
     } 
    } 
} 
0

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

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

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

1

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

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

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

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

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

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

+0

http://stackoverflow.com/questions/686630/static-generic-class-as-dictionary – SLaks 2011-01-03 16:14:51

2

Хорошо, ребята: Спасибо за всю рыбу. Теперь с вашими ответами и моими исследованиями (особенно статья на limiting generic types to primitives) я представлю вам мое решение.

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

Лучший код: ограничить 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 «Т» является параметр «типа», который не действует в данном контексте

SL

1

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

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

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

 Смежные вопросы

  • Нет связанных вопросов^_^