Как привести объект к типу, указанному в переменной типа - PullRequest
0 голосов
/ 18 февраля 2019

Я ищу способ приведения переменной object в тип с аргументом универсального типа, заданным другой переменной типа Type.

. Я ограничен .NET 3.5, поэтому нет dynamicможно использовать :( Основная идея здесь в том, что у меня есть доступ к словарю:

Dictionary<Type, object> data;

Данные в этот словарь добавляются только в виде:

data.Add(T, new DataSub<T>(someValueOfTypeT));

Проблема в том,что, когда я пытаюсь повернуть процесс вспять:

foreach(var dataType in data.Keys) {
  var dataValue = data[dataType];
  ProcessDataValue(dataType, dataValue);
}

Теперь возникает вопрос: как мне удается преобразовать объект в DataSub?

Упрощенный DataSub.cs:

public class DataSub<T>
{
  private T _cache;
  public T Value {
    get { return _cache; }
    set { _cache = value; }
  }
}

Как это может работать в ProcessDataValue:

public void ProcessDataValue(Type dataType, object dataValue)
{
  var data = dataValue as DataSub<dataType>;
  if (data == null) return;
  AddProcessedDataValue(dataType, data.Value.ToString());
}

1 Ответ

0 голосов
/ 20 февраля 2019

, если вы можете внести минимальные изменения в опубликованные вами классы, и , если - как показано в вашем примере, - то, что вы будете делать с DataSub.Value, вызывает ToString,возможно, вы можете получить нужный вам результат с помощью

    public interface IDataSub {
        bool MatchesType(Type t);

        object GetValue();
    }

    public class DataSub<T> : IDataSub {
        private T _cache;
        public T Value {
            get { return _cache; }
            set { _cache = value; }
        }

        public bool MatchesType(Type t) {
            return typeof(T) == t; // or something similar, in order to handle inheritance
        }

        public object GetValue() {
            return Value;
        }
    }

    public class Client {
        Dictionary<Type, IDataSub> data = new Dictionary<Type, IDataSub>() ;


        public void AddData<T>(T someValueOfTypeT) {
            data.Add(typeof(T), new DataSub<T> { Value = someValueOfTypeT });
        }


        public void UseData() {
            foreach(var dataType in data.Keys) {
                var dataValue = data[dataType];
                ProcessDataValue(dataType, dataValue);
            }
        }

        public void ProcessDataValue(Type dataType, IDataSub dataValue)
        {
            if(dataValue.MatchesType(dataType))
                AddProcessedDataValue(dataType, dataValue.GetValue().ToString());
        }
    }

Если использование DataSub.Value.ToString является лишь примером, а в реальном мире вам необходим доступ DataSub.Value используя его тип T, вам следует применить более широкую переработку вашего кода.

Что вы думаете о следующем подходе?Это приложение по шаблону, который мне нравится называть набор ответственности (я написал связанный пост на эту тему), вариант цепочки ответственности от GoF :

    public interface IDataSub {
        object GetValue();
    }

    public class DataSub<T> : IDataSub {
        private T _cache;
        public T Value {
            get { return _cache; }
            set { _cache = value; }
        }

        public object GetValue() {
            return Value;
        }
    }

    public interface IDataHandler {
        bool CanHandle(Type type);

        void Handle(object data);
    }

    public class Client {
        private readonly Dictionary<Type, IDataSub> data = new Dictionary<Type, IDataSub>();
        private readonly IList<IDataHandler> handlers = new List<IDataHandler>();


        public void AddData<T>(T someValueOfTypeT) {
            data.Add(typeof(T), new DataSub<T> { Value = someValueOfTypeT });
        }

        public void RegisterHandler(IDataHandler handler) {
            handlers.Add(handler);
        }


        public void UseData() {
            foreach(var dataType in data.Keys) {
                handlers.FirstOrDefault(h => h.CanHandle(dataType))?.Handle(data[dataType].GetValue());
            }
        }

        // Lambda-free version
//        public void UseData() {
//            foreach(var dataType in data.Keys) {
//                for (int i = 0; i < handlers.Count; i++) {
//                    if (handlers[i].CanHandle(dataType)) {
//                        handlers[i].Handle(data[dataType].GetValue());
//                        break; // I don't like breaks very much...
//                    }
//                }
//            }
//        }
    }

    class StringDataHandler : IDataHandler {
        public bool CanHandle(Type type) {
            // Your logic to check if this handler implements logic applyable to instances of type
            return typeof(string) == type;
        }

        public void Handle(object data) {
            string value = (string) data;
            // Do something with string
        }
    }

    class IntDataHandler : IDataHandler {
        public bool CanHandle(Type type) {
            // Your logic to check if this handler implements logic applyable to instances of type
            return typeof(int) == type;
        }

        public void Handle(object data) {
            int value = (int) data;
            // Do something with int
        }
    }

Этот подход позволяет вам отделить хранилище данных и логику итерации данных от логики обработки данных, специфичной для разных типов данных: * Реализациям IDataHandler известно, с каким типом данных они могут обрабатывать, и приведению общей object ссылки на желаемуютип.При желании вы можете объединить метод CanHandle с методом Handle, исключив прежний метод и изменив UseData на

public void UseData() {
    foreach(var dataType in data.Keys) {
        foreach(var handler in handlers) {
            handler.Handle(dataType, data[dataType].GetValue())
        }
    }
}

и реализации обработчика на

class IntDataHandler : IDataHandler {
        public void Handle(Type dataType, object data) {
            if(typeof(int) == type) {
                int value = (int) data;
                // Do something with int
            }
        }
    }

Thisвариант немного более безопасен для типа, потому что в первом варианте уже можно было вызывать метод Handle без предварительного вызова CanHandle.

Если вам понравился этот подход, вы можете привести еговперед, упрощая вашу структуру данных и преобразуя data из IDictionary в IList:

    public interface IDataSub {
        object GetValue();
    }

    public class DataSub<T> : IDataSub {
        private T _cache;
        public T Value {
            get { return _cache; }
            set { _cache = value; }
        }

        public object GetValue() {
            return Value;
        }
    }

    public interface IDataHandler {
        bool CanHandle(object data);

        void Handle(object data);
    }

    public class Client {
        private readonly IList<IDataSub> data = new List<IDataSub>();
        private readonly IList<IDataHandler> handlers = new List<IDataHandler>();


        public void AddData<T>(T someValueOfTypeT) {
            data.Add(new DataSub<T> { Value = someValueOfTypeT });
        }

        public void RegisterHandler(IDataHandler handler) {
            handlers.Add(handler);
        }


        public void UseData() {
            foreach(var dataItem in data) {
                var value = dataItem.GetValue();
                handlers.FirstOrDefault(h => h.CanHandle(value))?.Handle(value);
            }
        }

        // Lambda-free version as above...


    class StringDataHandler : IDataHandler {
        public bool CanHandle(object data) {
            // Your logic to check if this handler implements logic applyable to instances of String
            return data is string;
        }

        public void Handle(object data) {
            string value = (string) data;
            // Do something with string
        }
    }

    class IntDataHandler : IDataHandler {
        public bool CanHandle(Type type) {
            // Your logic to check if this handler implements logic applyable to instances of int
            return type is int;
        }

        public void Handle(object data) {
            int value = (int) data;
            // Do something with int
        }
    }

* * * * Без учета варианта CanHandle можно упростить интерфейс IDataHandler и его реализацию в этомкейс тоже ...

Надеюсь, мой ответ поможет вам решить сценарий разработки;Я строю его на подходе, который мне очень нравится, потому что он позволяет применять специфичную для подтипа логику к экземплярам разных классов, поскольку они имеют общий суперкласс (как object в моих примерах кода).

...