Как обойти ограничения в ограничениях универсального типа в C #? - PullRequest
6 голосов
/ 21 марта 2010

Хорошо, я ищу какой-то ввод, я почти уверен, что в настоящее время он не поддерживается в .NET 3.5, но здесь идет речь.

Я хочу, чтобы универсальный тип, передаваемый в мой класс, имел конструктор, подобный этому:

new(IDictionary<string,object>)

чтобы класс выглядел следующим образом

public MyClass<T>  where T : new(IDictionary<string,object>)
{
  T CreateObject(IDictionary<string,object> values)
  {
    return new T(values);
  }
}

Но компилятор этого не поддерживает, он не знает, о чем я спрашиваю.

Некоторые из вас могут спросить, почему вы хотите это сделать? Ну, я работаю над любимым проектом ORM, поэтому я получаю значения из БД, а затем создаю объект и загружаю значения.

Я думал, что было бы чище позволить объекту просто создавать себя со значениями, которые я ему даю. Насколько я могу судить, у меня есть два варианта:

1) Используйте рефлексию (которую я стараюсь избегать), чтобы получить массив PropertyInfo [], а затем используйте его для загрузки значений.

2) требует, чтобы T поддерживал такой интерфейс:

открытый интерфейс ILoadValues { void LoadValues ​​(значения IDictionary); }

и затем сделайте это

public MyClass<T> where T:new(),ILoadValues
{
  T CreateObject(IDictionary<string,object> values)
  {
    T obj = new T();
    obj.LoadValues(values);
    return obj;
  }
}

У меня проблема с интерфейсом, я думаю, философская, я не хочу показывать публичный метод для загрузки значений. Используя конструктор, идея заключалась в том, что если бы у меня был такой объект

namespace DataSource.Data
{
  public class User
  {
    protected internal User(IDictionary<string,object> values)
    {
      //Initialize
    }
  }
}

Пока MyClass<T> находится в одной сборке, конструктор будет доступен. Я лично считаю, что ограничение Типа, по моему мнению, должно спросить (есть ли у меня доступ к этому конструктору? Да, отлично!)

В любом случае, любые входные данные приветствуются.

Ответы [ 4 ]

8 голосов
/ 21 марта 2010

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

public class MyClass<T>
{
  public delegate T Factory(IDictionary<string, object> values);

  private readonly Factory _factory;

  public MyClass(Factory factory)
  {
    _factory = factory;
  }

  public T CreateObject(IDictionary<string, object> values)
  {
    return _factory(values);
  }
}

Используется следующим образом:

MyClass<Bob> instance = new MyClass<Bob>(dict => new Bob(dict));
Bob bob = instance.CreateObject(someDictionary);

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

2 голосов
/ 21 марта 2010

Если вы можете создать общий базовый класс для всех объектов, которые вы собираетесь передать в MyClass в качестве параметров типа, вы можете сделать следующее:

internal interface ILoadValues
{
    void LoadValues<TKey, TValue>(IDictionary<TKey, TValue> values);
}

public class Base : ILoadValues
{
    void ILoadValues.LoadValues<TKey, TValue>(IDictionary<TKey, TValue> values)
    {
        // Load values.
    }
}

public class MyClass<T>
    where T : Base, new()
{
    public T CreateObject(IDictionary<string,object> values)
    {
        ILoadValues obj = new T();
        obj.LoadValues(values);
        return (T)obj;
    }
}

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

1 голос
/ 21 марта 2010

Мне действительно любопытно, как бы вы загружали значения класса, не используя отражения, если у вас не было методов, жестко закодированных для этого.Я уверен, что есть другой ответ, но мне не стыдно сказать, что у меня нет опыта в этом.Что касается того, что я написал для автоматической загрузки данных, у меня есть два базовых класса данных, из которых я работаю: один объект и затем список.В одном объекте (BaseDataClass) у меня есть этот метод.

    public virtual void InitializeClass(DataRow dr)
    {
        Type type = this.GetType();
        PropertyInfo[] propInfos = type.GetProperties();

        for (int i = 0; i < dr.ItemArray.GetLength(0); i++)
        {
            if (dr[i].GetType() != typeof(DBNull))
            {
                string field = dr.Table.Columns[i].ColumnName;
                foreach (PropertyInfo propInfo in propInfos)
                {
                    if (field.ToLower() == propInfo.Name.ToLower())
                    {
                        // get data value, set property, break
                        object o = dr[i];
                        propInfo.SetValue(this, o, null);
                        break;
                    }
                }
            }
        }
    }

А затем в списке данных

public abstract class GenericDataList<T> : List<T> where T : BaseDataClass
{
    protected void InitializeList(string sql)
    {
        DataHandler dh = new DataHandler(); // my general database class
        DataTable dt = dh.RetrieveData(sql); 
        if (dt != null)
        {
            this.InitializeList(dt);
            dt.Dispose();
        }
        dt = null;
        dh = null;
    }

    protected void InitializeList(DataTable dt)
    {
        if (dt != null)
        {
            Type type = typeof(T);
            MethodInfo methodInfo = type.GetMethod("InitializeClass");

            foreach (DataRow dr in dt.Rows)
            {
                T t = Activator.CreateInstance<T>();
                if (methodInfo != null)
                {
                    object[] paramArray = new object[1];
                    paramArray[0] = dr;
                    methodInfo.Invoke(t, paramArray);
                }

                this.Add(t);
            }
        }
    }
}

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

Редактировать: Знаете что?Глядя на это сейчас, я не понимаю, почему я не должен просто переписать этот последний метод как

        protected void InitializeList(DataTable dt)
        {
            if (dt != null)
            {
                Type type = typeof(T);

                foreach (DataRow dr in dt.Rows)
                {
                    T t = Activator.CreateInstance<T>();
                    (t as BaseDataClass).InitializeClass(dr);

                    this.Add(t);
                }
            }
        }

Я предполагаю, что это работает, хотя я не проверял его.Нет необходимости использовать отражение в этой части.

1 голос
/ 21 марта 2010

Вы не можете этого сделать. new (конструктор) ограничения только для конструкторов без параметров. Вы не можете иметь ограничение для конструктора с конкретными параметрами.

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

(Я надеюсь, что кто-то здесь нашел более элегантный ответ на эту проблему!)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...