Храните словарь <Type, MyClass <T>>, где элементы ссылочными по типу

У меня есть абстрактный класс EntityTypeTransform с единственным абстрактным методом, предназначенным для размещения делегата Func, который преобразует IDataRecord в экземпляр T.

public abstract class EntityTypeTransform<TEntityType> where TEntityType : class
{
    public abstract Func<IDataRecord, TEntityType> GetDataTransform();
}

Реализация этого класса может выглядеть (выглядит так):

public class TaskParameterEntityTypeTransform : EntityTypeTransform<TaskParameter>
{
    public override Func<IDataRecord, TaskParameter> GetDataTransform()
    {
        return dataRecord => new TaskParameter()
        {
            TaskId = (int)dataRecord["task_id"],
            Name = (string)dataRecord["p_name"],
            Value = (string)dataRecord["p_value"]
        };
    }
}

Теперь я хочу сохранить экземпляр каждого из этих классов в общем словаре, например:

Dictionary<Type, EntityTypeTransform<T>>

Но это не работает, потому что (например) экземпляр EntityTypeTransform of Task не совпадает с экземпляром EntityTypeTransform TaskParameter.

Может ли кто-нибудь помочь мне?

Изменить: я должен добавить, что Type key = typeof (T)

Ответы

Ответ 1

На самом деле, вам не нужно использовать словарь вообще! Вы можете использовать тот факт, что GenericClass<T> на самом деле является другим типом для каждого T, поэтому он может иметь свои собственные статические поля (т.е. GenericClass<Foo>.SomeField не используется совместно с GenericClass<Bar>.SomeField)

Например, вы можете реализовать свой кеш следующим образом:

static class TransformCache<TEntityType>
{
    public static EntityTypeTransform<TEntityType> Transform { get; set; }
}

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

TransformCache<TaskParameter>.Transform = new TaskParameterEntityTypeTransform();

Ответ 2

Вы не можете указать массивную типизированную коллекцию, которая будет содержать разные типы. Здесь подход, который я использовал в подобной проблеме, изменен в соответствии с вашим требованием:

class TransformCollection
{
   private Hashtable cache = new Hashtable();

   public void Add<T>(EntityTypeTransform<T> transform) where T : class
   {
      this.cache[typeof(T)] = itemToCache;
   }

   public bool Exists<T>() where T : class
   {
      return this.cache.ContainsKey(typeof(T));
   }

   public EntityTypeTransform<T> Get<T>() where T : class
   {
      if (!this.Exists<T>())
         throw new ArgumentException("No cached transform of type: " + typeof(T).Name);
      return this.cache[typeof(T)] as EntityTypeTransform<T>;
   }
}

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

var collection = new TransformCollection();
collection.Add(SomeMethodToGetTransform<Task>());
//...
if (collection.Exists<Task>())
{
   var transform = collection.Get<Task>();
   //...
}

Ответ 3

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

public interface IEntityTypeTransform
{
    Func<IDataRecord, object> GetDataTransform();
}

public abstract class EntityTypeTransform<TEntityType> : IEntityTypeTransform
    where TEntityType : class
{
    public virtual Func<IDataRecord, TEntityType> GetDataTransform()
    {
        return this.GetDataTransformImpl();
    }

    public abstract Func<IDataRecord, TEntityType> GetDataTransformImpl();

    Func<IDataRecord, object> IEntityTypeTransform.GetDataTransform()
    {
        return this.GetDataTransform();
    }
}

Ответ 4

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

public abstract class EntityTypeTransformBase
{
    public abstract Func<IDataRecord, object> GetDataTransform();
}

public abstract class EntityTypeTransform<TEntityType> : EntityTypeTransformBase where TEntityType : class
{
    public abstract Func<IDataRecord, TEntityType> GetDataTransformImpl();

    public override Func<IDataRecord, object> GetDataTransform()
    {
        return GetDataTransformImpl();
    }
}

public class TaskParameterEntityTypeTransform : EntityTypeTransform<TaskParameter>
{
    public override Func<IDataRecord, TaskParameter> GetDataTransformImpl()
    {
        return dataRecord => new TaskParameter()
        {
            TaskId = (int)dataRecord["task_id"],
            Name = (string)dataRecord["p_name"],
            Value = (string)dataRecord["p_value"]
        };
    }
}

Теперь вы можете создать словарь:

var d = new Dictionary<Type, EntityTypeTransformBase>();
d.Add(typeof(TaskParameter), new TaskParameterEntityTypeTransform());

Ответ 5

Вы можете использовать KeyedByTypeCollection, чтобы получить безопасность по типу, и вы можете определить интерфейс с параметром ковариационного типа, чтобы убедиться, что только объекты типа EntityTypeTransform<T> можно добавить в словарь:

public interface IEntityTypeTransform<out TEntityType> where TEntityType : class
{
    TEntityType Transform(IDataRecord dataRecord);
}

public abstract class EntityTypeTransform<TEntityType> : IEntityTypeTransform<TEntityType> where TEntityType : class
{
    public abstract TEntityType Transform(IDataRecord dataRecord);
}

public class TaskParameter
{
    public int TaskId;
    public string Name;
    public string Value;
}

public class TaskParameterEntityTypeTransform : EntityTypeTransform<TaskParameter>
{
    public override TaskParameter Transform(IDataRecord dataRecord)
    {
        return new TaskParameter()
        {
            TaskId = (int)dataRecord["task_id"],
            Name = (string)dataRecord["p_name"],
            Value = (string)dataRecord["p_value"]
        };
    }
}

public class SomeClass
{
    public KeyedByTypeCollection<IEntityTypeTransform<object>> TransformDictionary = new KeyedByTypeCollection<IEntityTypeTransform<object>>()
    {
        new TaskParameterEntityTypeTransform(),
        // More transforms here
    };
}

Теперь вы можете использовать его следующим образом:

public void SomeMethod(IDataRecord dataRecord)
{
    TaskParameter taskParameter = TransformDictionary.Find<TaskParameterEntityTypeTransform>().Transform(dataRecord);
}

Ответ 6

Добавьте к вашим трансформаторам не общий интерфейс:

public interface IEntityTypeTransform
{
    Func<IDataRecord, object> GetDataTransform();
}
public abstract class EntityTypeTransform<T> : IEntityTypeTransform
{
    public abstract Func<IDataRecord, object> GetDataTransform();
}
public class TaskParameterEntityTypeTransform : EntityTypeTransform<TaskParameter>
{
    public override Func<IDataRecord, object> GetDataTransform()
    {
        return dataRecord => new TaskParameter()
        {
            TaskId = (int)dataRecord["task id"],
        };
    }
}

Затем вы можете инкапсулировать словарь, чтобы гарантировать соответствие типов данных. Никогда не позволяйте добавлять IEntityTypeTransform плохого типа:

public class TransformDistributor
{
    private readonly Dictionary<Type, IEntityTypeTransform> _transforms = new Dictionary<Type, IEntityTypeTransform>();
    public void Add<T>(EntityTypeTransform<T> type)
    {
        this._transforms.Add(typeof(T), type);
    }
    public T Transform<T>(IDataRecord record)
    {
        var transform = this._transforms[typeof(T)].GetDataTransform()(record);
        if (transform is T)
        {
            return (T)transform;
        }
        else
        {
            // theorically can't happen
            throw new InvalidOperationException("transformer doesn't return instance of type " + transform.GetType().Name);
        }
    }
}

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

Использование:

var transforms = new TransformDistributor();
transforms.Add<TaskParameter>(new TaskParameterEntityTypeTransform());

var taskParameter = transforms.Transform<TaskParameter>(new DataRecord());

Ответ 7

Я попытался понять, чего вы точно хотите, я надеюсь, что это именно то, что вы ищете!

Вы должны установить в классе TaskParameter правильные параметры: TaskId, Name, Value

public abstract class EntityTypeTransform<TEntityType> where TEntityType : class
{
  public abstract Func<IDataRecord, TEntityType> GetDataTransform();
}

public class TaskParameterEntityTypeTransform : EntityTypeTransform<TaskParameter>
{
  public override Func<IDataRecord, TaskParameter> GetDataTransform()
  {
    return x => new TaskParameter { X = x.FieldCount };
  }
}

public class TaskParameter
{
  public int X { get; set; }
}

Dictionary<Type, EntityTypeTransform<TaskParameter>> imADict;