简体   繁体   中英

Keep a Dictionary<Type, MyClass<T>> where elements are referenceable by type

I have an abstract class called EntityTypeTransform with a single abstract method designed to hold a Func delegate that converts an IDataRecord into an instance of T.

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

An implementation of that class might look like (does look like) this:

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"]
        };
    }
}

Now I want to keep an instance of each of these classes in a generic Dictionary, something like:

Dictionary<Type, EntityTypeTransform<T>>

But this doesn't work because (for example) an instance of EntityTypeTransform Of Task is not the same as an instance of EntityTypeTransform Of TaskParameter.

Can anyone help me out?

Edit: I should add that the Type key = typeof(T)

Actually, you don't need to use a dictionary at all! You can use the fact that GenericClass<T> is actually a different type for each T, so it can have its own static fields (ie GenericClass<Foo>.SomeField is not shared with GenericClass<Bar>.SomeField )

For instance you can implement your cache like this:

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

And use it like this:

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

You can't specify a strong-typed collection that would hold different generic types. Here's the approach I've used in a similar problem, modified to match your requirement:

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

This gives you type-safe cache for your generic type (though type-safety is enforced by the class's logic, not C#). You can use it as follows:

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

You could use an interface that is non-generic and then implement that interface explicitly inside that abstract class, It's pretty common in the .Net library itself:

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

You would have to create a non-generic base class, eg

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"]
        };
    }
}

Now you can create your dictionary:

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

You can use KeyedByTypeCollection to get type-safety and you can define an interface with a covariant type parameter to make sure that only objects of type EntityTypeTransform<T> can be added to the dictionary:

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

Now you can use it like this:

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

Add a non generic interface to your transformers:

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"],
        };
    }
}

Then you can encapsulate your dictionary for ensure that datatypes will always match. Never allow to add a IEntityTypeTransform of a bad type :

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

The advantage are that at compile time, your are sure that nobody can insert a bad transformer, even if your are not using generics.

Usage :

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

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

I have tried to understand what you exactly want I hope this is exactly what you are looking for!

You shall set in TaskParameter class the correct parameters: 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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM