Refactoring functions to make more generic - c#

I have the following code copy/pasted multiple times. The values that change are the string literals ("TabStates" changes to "ContentStates" etc..) and the value of the dictionary (RadTabSetting -> ContentSetting).
public static SerializableDictionary<string, RadTabSetting> GetTabStates()
{
SerializableDictionary<string, RadTabSetting> _tabStates = new SerializableDictionary<string, RadTabSetting>();
if (!object.Equals(DashboardSessionRepository.Instance.GetSession("TabStates"), null))
{
_tabStates = DashboardSessionRepository.Instance.GetSession("TabStates") as SerializableDictionary<string, RadTabSetting>;
}
else
{
XmlSerializer serializer = new XmlSerializer(_tabStates.GetType());
string data = DashoardDatabaseRepository.Instance.GetWebLayoutData("TabStates");
if ( !string.IsNullOrEmpty(data) )
{
byte[] dataAsArray = Convert.FromBase64String(data);
MemoryStream stream = new MemoryStream(dataAsArray);
_tabStates = serializer.Deserialize(stream) as SerializableDictionary<string, RadTabSetting>;
}
DashboardSessionRepository.Instance.SetSession("TabStates", _tabStates);
}
return _tabStates;
}
public static void SetTabStates(SerializableDictionary<string, RadTabSetting> tabStates)
{
DashboardSessionRepository.Instance.SetSession("TabStates", tabStates);
DashboardDatabaseRepository.Instance.SaveToDatabase("TabStates", tabStates);
}
I'm not looking for an answer, just curious what I should read about to learn how to rewrite this. I'm sure it's simple enough, just not sure what it's called. Is it just function templating?
public static T GetStates<T>() where T: new()
{
T _states = new T();//(T)Activator.CreateInstance(typeof(T));
string stateName = StateDictionary.GetStateName(typeof(T));
if (!object.Equals(DashboardSessionRepository.Instance.GetSession(stateName), null))
{
_states = (T)DashboardSessionRepository.Instance.GetSession(stateName);
//Work-Around
System.IO.MemoryStream memoryStream = new System.IO.MemoryStream();
System.Xml.Serialization.XmlSerializer xmlSerializer = new System.Xml.Serialization.XmlSerializer(_states.GetType());
xmlSerializer.Serialize(memoryStream, _states);
string data = System.Convert.ToBase64String(memoryStream.ToArray());
string otherData = DashboardDatabaseRepository.Instance.GetWebLayoutData(stateName);
if (!string.IsNullOrEmpty(data))
{
XmlSerializer serializer = new XmlSerializer(_states.GetType());
byte[] dataAsArray = Convert.FromBase64String(data);
MemoryStream stream = new MemoryStream(dataAsArray);
_states = (T)serializer.Deserialize(stream);
}
//Work-Around
}
else
{
XmlSerializer serializer = new XmlSerializer(_states.GetType());
string data = DashboardDatabaseRepository.Instance.GetWebLayoutData(stateName);
if (!string.IsNullOrEmpty(data))
{
byte[] dataAsArray = Convert.FromBase64String(data);
MemoryStream stream = new MemoryStream(dataAsArray);
_states = (T)serializer.Deserialize(stream);
}
DashboardSessionRepository.Instance.SetSession(stateName, _states);
}
return _states;
}
public static void SetStates<T>(T states) where T: new()
{
string stateName = StateDictionary.GetStateName(typeof(T));
DashboardSessionRepository.Instance.SetSession(stateName, states);
DashboardDatabaseRepository.Instance.SaveToDatabase(stateName);
}
static class StateDictionary
{
//TODO: Might (should?) be able to redo this polymorphically.
static IDictionary<Type, string> _stateDictionary = new Dictionary<Type, string>
{
{typeof(SerializableDictionary<string, RadTabSetting>), "TabStates"},
{typeof(SerializableDictionary<string, RadDockContentSetting>), "ContentStates"},
{typeof(SerializableDictionary<string, RadPaneSetting>), "PaneStates"},
{typeof(SerializableDictionary<string, RadDockSetting>), "DockStates"},
{typeof(SerializableDictionary<string, RadDockZoneSetting>), "DockZoneStates"},
{typeof(SerializableDictionary<string, RadSplitterSetting>), "SplitterStates"},
{typeof(SerializableDictionary<string, RadSplitBarSetting>), "SplitBarStates"},
{typeof(KnownGlobalSettings), "GlobalSettings"},
};
public static string GetStateName(Type type)
{
string stateName = string.Empty;
if (_stateDictionary.ContainsKey(type))
{
stateName = _stateDictionary[type];
}
return stateName;
}
}

Martin Fowler's book is the standard source for refactoring.
But for your specific example, just create a new method that takes in parameters representing the things that change in your example. So you'll want a string parameter for your "TabStates" or "ContentStates" value, and another representing your Value for Key ContentSetting in your dictionary.
Does that make sense? Am I understanding your question fully?
EDIT
Based on your comment, you want to use generics. Something like this should get you going:
public static Dictionary<string, T> GetTabStates<T>()
Just note that you won't be able to do much with your type T unless you add some generic constraints.
If you want to create a new instance of T, then you would need
public static Dictionary<string, T> GetTabStates<T>() where T : new() {
And if you want to access actual properties on an instance of T, then hopefully all possible values for T will implement some sort of interface, in which case you would say:
public interface IFoo {
int Id { get; set; }
}
public static Dictionary<string, T> GetTabStates<T>() where T : new(), IFoo {
T Tval = new T();
Tval.Id = 1;
//etc

Related

A constructor that takes object from same type

So I want to create constructor for my class EmployeeNodeClass that takes In EmployeeNodeClass object and copies it using a deepclone funtion:
public static T DeepClone<T>(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
to the new object.
At first i thought that it is as simple as
public EmployeeNodeClass(EmployeeNodeClass EMPND)
{
this = DeepClone(EMPND);
}
but then I got the error that this is readonly.
so how can I do it?
It can be done using NewtonSoft JsonConvert.PopulateObject:
Example:
class Entity
{
public List<int> List { get; set; }
public Entity(List<int> list)
{
List = list;
}
public Entity(Entity original)
{
string originalJson = JsonConvert.SerializeObject(original);
JsonConvert.PopulateObject(originalJson, this);
}
}
And using it:
static void Main(string[] args)
{
var entity1 = new Entity(new List<int> { 1, 2, 3 });
var entity2 = new Entity(entity1);
entity1.List[0] = 5;
Console.WriteLine(entity2.List[0]);
}
Note: since this uses NewtonSoft Json, it will only clone public, writable properties. So internal state in private fields (that are not associated with such properties) will be lost.

Failing to read XML content using XmlSerializer

I got this in my class:
namespace MSAToolsLibrary.PublisherEntry
{
[XmlRoot(ElementName = "PublisherDatabase", Namespace = "http://www.publictalksoftware.co.uk/msa")]
public class PublisherData
{
public PublisherData()
{
//_Publishers = new List<Publisher>();
_PublishersDictionary = new Dictionary<string, Publisher>();
}
public List<Publisher> Publishers
{
get { return _PublishersDictionary.Select(x => x.Value).ToList(); }
set { _PublishersDictionary = value.ToDictionary(x => x.Name, x => x); }
}
private Dictionary<string, Publisher> _PublishersDictionary;
[XmlIgnore]
public Dictionary<string, Publisher> PublisherDictionary
{
get { return _PublishersDictionary; }
}
public void AddPublisher(String strName, String strNotes, Gender eGender, Appointed eAppointedAs, Serving eServingAs, bool bUseForDemonstrations, bool bAvailableMidweek, bool bAvailableWeekend, DateTime[] listDatesNotAvailable)
{
Publisher _Publisher = new Publisher()
{
Name = strName,
Notes = strNotes,
Gender = eGender,
AppointedAs = eAppointedAs,
ServingAs = eServingAs,
};
_Publisher.Assignments.UseForDemonstrations = bUseForDemonstrations;
_Publisher.Availability.Midweek = bAvailableMidweek;
_Publisher.Availability.Weekend = bAvailableWeekend;
_Publisher.Availability.DatesNotAvailable = new List<DateTime>(listDatesNotAvailable);
//_Publishers.Add(_Publisher);
_PublishersDictionary.Add(strName, _Publisher);
}
}
}
Now, when I save my data to XML it all works good.
But when I read in:
public void ReadPublisherData(out Int64 iResult)
{
_PublisherData.Publishers.Clear(); // Reset
iResult = MakeResult(true);
try
{
XmlSerializer x = new XmlSerializer(_PublisherData.GetType());
using (StreamReader reader = new StreamReader(_strPathXML))
{
_PublisherData = (PublisherData)x.Deserialize(reader);
iResult = _PublisherData.PublisherDictionary.Count;
}
}
catch
{
iResult = MakeResult(false);
}
}
Doesn't work. I have zero publishers in the list or the dictionary.
What am I doing wrong?
Update
If I change the PublisherData declaration so that it has the needed back field:
public PublisherData()
{
_Publishers = new List<Publisher>();
_PublishersDictionary = new Dictionary<string, Publisher>();
}
public List<Publisher> Publishers
{
get => _Publishers; set => _Publishers = value;
}
private List<Publisher> _Publishers;
Then this causes the data to serialize correctly and I get what is expected in the MFC application. But now my PublisherDictionary is hanging. So I added a function:
public void BuildPublisherDictionary()
{
_PublishersDictionary = _Publishers.ToDictionary(x => x.Name, x => x);
}
And adjusted the read routine:
public void ReadPublisherData(out Int64 iResult)
{
iResult = MakeResult(true);
try
{
_PublisherData.Publishers.Clear(); // Reset
XmlSerializer x = new XmlSerializer(_PublisherData.GetType());
using (StreamReader reader = new StreamReader(_strPathXML))
{
_PublisherData = (PublisherData)x.Deserialize(reader);
_PublisherData.BuildPublisherDictionary();
iResult = _PublisherData.PublisherDictionary.Count;
}
}
catch
{
iResult = MakeResult(false);
}
}
It works. But I don't know if that is the simplest way. My problem with the above is that the list / dictionary are now detached from each other. So if I add a publisher to the dictionary it will now not be in the list.
Update 2
At the moment, after reading in the XML, if I add or remove a Publisher I am applying it to both the list and the dictionary. Unless there is a simpler way.
The serializer will only call the getter of your List and then call Add for each element it finds, providing it the instance of the type it deserialized from that element.
If your requirement is to have both a list and a dictionary you'll have to provide an implementation of an type that does so and implements an applicable interface.
The following ListDict uses a List and a Dictionary as their backing stores while implementing IList to be used in the (de)serializtion.
public class ListDict<K, T>:IList<T>
{
public Dictionary<K, T> dict = new Dictionary<K, T>();
public List<T> list = new List<T>();
Func<T,K> KeyFunc;
// takes an Function that returns a key for T
public ListDict(Func<T,K> keyfunc)
{
KeyFunc = keyfunc;
}
// called by the serializer
public void Add(T value)
{
Add( KeyFunc(value), value);
}
// fill both List and Dictionary
public void Add(K key, T value)
{
list.Add(value);
dict.Add( key , value);
}
// left out other required methods from IList<T>
}
Now your PublisherData class will change as follows to leverage above class:
[XmlRoot(ElementName = "PublisherDatabase", Namespace = "http://www.publictalksoftware.co.uk/msa")]
public class PublisherData
{
private ListDict<string, Publisher> _PublishersDictionary;
public PublisherData()
{
// provide the function to generate a key for a Publisher
_PublishersDictionary = new ListDict<string,Publisher>( (p) => p.Name );
}
[XmlElement]
public ListDict<string,Publisher> Publishers
{
get { return _PublishersDictionary; }
}
[XmlIgnore]
public Dictionary<string, Publisher> PublisherDictionary
{
get {return _PublishersDictionary.dict; }
}
}
Using above classes gives me a filled list and Dictionary directly after deserialization. You'll have to make sure of course to keep the backing stores in sync in the ListDict. Maybe you can do without it but that depends on your exact usecase.

XML Deserialize items of generic types

Assume I have the following class:
public abstract class ScheduledService : ScheduledServiceBase<ScheduledService>
{
public CronInfo CronInfo;
public String ServiceName;
public ScheduledService()
{ }
}
public abstract class ScheduledServiceBase<T>
{
public ScheduledServiceBase()
{ }
public virtual void StartUp(IScheduler scheduler, ScheduledService service, Dictionary<string, object> parameters = null)
{
...
}
}
From this base class I create two inheriting classes like so:
public AlphaService : ScheduledService
{
public String Alpha_Name;
public Int32 Alpha_Age;
public AlphaService() { }
}
public BetaService : ScheduledService
{
public String Beta_Company;
public Boolean Beta_IsOpen;
public AlphaService() { }
}
Then in my XML I define the two Services:
<ScheduledServices>
<AlphaService>
<Alpha_Name>John Jones</Alpha_Name>
<Alpha_Age>32</Alpha_Age>
<ServiceName>FirstService</ServiceName>
<CronInfo>0 0 0/2 * * ? *</CronInfo>
</AlphaService>
<BetaService>
<Beta_Company>Ajax Inc.</Beta_Company>
<Beta_IsOpen>Ajax Inc.</Beta_IsOpen>
<ServiceName>SecondService</ServiceName>
<CronInfo>0 30 0/5 * * ? *</CronInfo>
</BetaService>
</ScheduledService>
When I deserialize this I am trying to assign the ScheduledServices to List<ScheduledService> ScheduledServices but nothing exists in the list after deserialization. The deserializer doesn't error out, it just doesn't create any services.
Is what I am trying to do valid or do I need to setup the services differently?
Here is what I am using to deserialize the XML:
public Boolean Load(params Type[] extraTypes)
{
deserializedObject = default(T);
XmlTextReader reader = null;
try
{
reader = new XmlTextReader(new StreamReader(_actualPath));
XmlSerializer serializer = new XmlSerializer(typeof(T), extraTypes);
deserializedObject = (T)(serializer.Deserialize(reader));
}
catch (Exception ex)
{
log.Error("Error: " + ex.Message);
log.Debug("Stack: " + ex.StackTrace);
log.Debug("InnerException: " + ex.InnerException.Message);
}
finally
{
if (reader != null) reader.Close();
}
return ((this.deserializedObject != null) && deserializedObject is T);
}
SOLUTION
Thanks to John Arlen and his example and link to another post I was able to do what I wanted by creating the list as follows:
[XmlArrayItem("AlphaJob", Type=typeof(AlphaJob))]
[XmlArrayItem("BetaJob", Type=typeof(BetaJob))]
public List<ScheduledService> ScheduledServices;
The XmlSerializer class needs more information than your XML is offering to work properly.
For example, it does not know exactly which Type is referred to by "AlphaService" ("MyNamespace.AlphaService, MyUtility"?)
The easiest way to start is create a sample object in memory and call XmlSerializer.Serialize(myObj) to understand the actual format it is looking for.
If you have no control over the incoming XML, you will either need to further decorate your classes (With XmlElementAttribute, for example), or use a different mechanism
EDIT: An example of marking up your class might be:
[XmlInclude( typeof( AlphaService ) )]
[XmlInclude( typeof( BetaService ) )]
public abstract class ScheduledService : ScheduledServiceBase<ScheduledService> {...}
And you can test this by serializing it to XML and back into a concrete object:
private void SerializeAndBack()
{
var item = new ScheduledServiceHost
{
ScheduledServices = new List<ScheduledService>
{
new AlphaService {Alpha_Age = 32, Alpha_Name = "John Jones", ServiceName = "FirstService"},
new BetaService {Beta_Company = "Ajax Inc.", Beta_IsOpen = true, ServiceName = "SecondService"},
}
};
var xmlText = XmlSerializeToString( item );
var newObj = XmlDeserializeFromString( xmlText, typeof( ScheduledServiceHost ) );
}
public static string XmlSerializeToString( object objectInstance, params Type[] extraTypes )
{
var sb = new StringBuilder();
using ( TextWriter writer = new StringWriter( sb ) )
{
var serializer = new XmlSerializer( objectInstance.GetType(), extraTypes );
serializer.Serialize( writer, objectInstance );
}
return sb.ToString();
}
public static object XmlDeserializeFromString( string objectData, Type type )
{
using ( var reader = new StringReader( objectData ) )
{
return new XmlSerializer( type ).Deserialize(reader);
}
}
Here is a good SO answer on some options for marking up a class is.

Serializing a HashSet in a Dictionary

I have a similar Problem to Serializing a HashSet
I have a Class with a Member of type Dictionary<String,HashSet<T>>
When i serialize the Object with BinaryFormatter, and then deserialize it, it comes up empty.
I don't know how, or where to call the workaround posted here.
Any Hints? thanks in advance.
edit:
i tried to convert the hashset to a list, as one of the comments in the other thread suggested.
the object looks like this:
public class THashSet : HashSet<T> , ISerializable
{
public THashSet(SerializationInfo info, StreamingContext context)
{
var list = (List<T>)info.GetValue("hashset", typeof(List<T>));
foreach (T t in list)
this.Add(t);
}
public override void GetObjectData(SerializationInfo info,StreamingContext context)
{
info.AddValue("hashset", this.ToList<T>());
}
when the object containing the THashSet is deserialized (and the constructor is called), the list is recovered correctly, as i see in the debugger.
but after the serializer is done, the object only contains an empty hashset.
Suppose for your T object you haven't override methods GetHashCode and Equals. You need to do it.
UPD: All depends from your object implementation. And object is not so easy as you say. Your sitation, object:
[Serializable]
public class DataClass
{
public DataClass()
{
}
public DataClass(string name, string description)
{
Name = name;
Description = description;
this.Dictionary = new Dictionary<string, HashSet<DataClass>>();
}
public string Name { get; set; }
public string Description { get; set; }
public Dictionary<string, HashSet<DataClass>> Dictionary { get; set; }
}
And serialization/deserialization code:
DataClass dataClass = new DataClass("name", "descr");
dataClass.Dictionary.Add("key1", new HashSet<DataClass>() { new DataClass("sample11", "descr11"), new DataClass("sample12", "descr12") });
dataClass.Dictionary.Add("key2", new HashSet<DataClass>() { new DataClass("sample21", "descr21"), new DataClass("sample22", "descr22") });
dataClass.Dictionary.Add("key3", new HashSet<DataClass>() { new DataClass("sample31", "descr31"), new DataClass("sample32", "descr32") });
byte[] serialized;
var formatter = new BinaryFormatter();
using (MemoryStream stream = new MemoryStream())
{
formatter.Serialize(stream, dataClass);
serialized = stream.ToArray();
}
using (MemoryStream streamDeserial = new MemoryStream())
{
streamDeserial.Write(serialized, 0, serialized.Length);
streamDeserial.Seek(0, SeekOrigin.Begin);
var dictDeserial = formatter.Deserialize(streamDeserial) as DataClass;
}
This code works well.

.NET binary XML with pre-shared dictionary

I'm using XmlDictionaryWriter to serialize objects to a database with data contract serializer.
It works great, both size and speed are 2 times better then using text/xml.
However, I'll have to deal with enormous count of records in my database, where any extra bytes are directly translated into the gigabytes of the DB size.
That's why I'd love to reduce the size further, by using an XML dictionary.
How do I do that?
I see that XmlDictionaryWriter.CreateBinaryWriter static method accepts the 2-nd parameter of type IXmlDictionary. The MSDN says "The XmlDictionary to use as the shared dictionary".
First I've tried to use the system-supplied implementation:
XmlDictionary dict = new XmlDictionary();
string[] dictEntries = new string[]
{
"http://schemas.datacontract.org/2004/07/MyContracts",
"http://www.w3.org/2001/XMLSchema-instance",
"MyElementName1",
"MyElementName2",
"MyElementName3",
};
foreach ( string s in dictEntries )
dict.Add( s );
The result is .NET framework completely ignores the dictionary, and still inserts the above strings as plain text instead of just referencing a corresponding dictionary entry.
Then I've created my own implementation of IXmlDictionary:
class MyDictionary : IXmlDictionary
{
Dictionary<int, string> values = new Dictionary<int, string>();
Dictionary<string, int> keys = new Dictionary<string, int>();
MyDictionary()
{
string[] dictEntries = new string[]
{
"http://schemas.datacontract.org/2004/07/MyContracts",
"http://www.w3.org/2001/XMLSchema-instance",
"MyElementName1",
"MyElementName2",
"MyElementName3",
};
foreach ( var s in dictEntries )
this.Add( s );
}
static IXmlDictionary s_instance = new MyDictionary();
public static IXmlDictionary instance { get { return s_instance; } }
void Add( string val )
{
if ( keys.ContainsKey( val ) )
return;
int id = values.Count + 1;
values.Add( id, val );
keys.Add( val, id );
}
bool IXmlDictionary.TryLookup( XmlDictionaryString value, out XmlDictionaryString result )
{
if ( value.Dictionary == this )
{
result = value;
return true;
}
return this.TryLookup( value.Value, out result );
}
bool IXmlDictionary.TryLookup( int key, out XmlDictionaryString result )
{
string res;
if ( !values.TryGetValue( key, out res ) )
{
result = null;
return false;
}
result = new XmlDictionaryString( this, res, key );
return true;
}
public bool /* IXmlDictionary. */ TryLookup( string value, out XmlDictionaryString result )
{
int key;
if ( !keys.TryGetValue( value, out key ) )
{
result = null;
return false;
}
result = new XmlDictionaryString( this, value, key );
return true;
}
}
The result is - my TryLookup methods are called OK, however DataContractSerializer.WriteObject produces an empty document.
How do I use a pre-shared dictionary?
Thanks in advance!
P.S. I don't want to mess with XmlBinaryReaderSession/XmlBinaryWriterSession: I don't have "sessions", instead I have a 10 GB+ database accessed by many threads at once. What I want is just static pre-defined dictionary.
Update: OK I've figured out that I just need to call "XmlDictionaryWriter.Flush". The only remaining question is - why doesn't the system-supplied IXmlDictionary implementation work as expected?
for the XmlDictionaryWriter you need to use session.
example:
private static Stream SerializeBinaryWithDictionary(Person person,DataContractSerializer serializer)
{
var stream = new MemoryStream();
var dictionary = new XmlDictionary();
var session = new XmlBinaryWriterSession();
var key = 0;
session.TryAdd(dictionary.Add("FirstName"), out key);
session.TryAdd(dictionary.Add("LastName"), out key);
session.TryAdd(dictionary.Add("Birthday"), out key);
session.TryAdd(dictionary.Add("Person"), out key);
session.TryAdd(dictionary.Add("http://www.friseton.com/Name/2010/06"),out key);
session.TryAdd(dictionary.Add("http://www.w3.org/2001/XMLSchema-instance"),out key);
var writer = XmlDictionaryWriter.CreateBinaryWriter(stream, dictionary, session);
serializer.WriteObject(writer, person);
writer.Flush();
return stream;
}
The only way I way able to replicate the issue with the IXmlDictionary not being used was when my class wasn't decorated with a DataContract attribute. The following app displays the difference in sizes with decorated and undecorated classes.
using System;
using System.Runtime.Serialization;
using System.Xml;
namespace XmlPresharedDictionary
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Serialized sizes");
Console.WriteLine("-------------------------");
TestSerialization<MyXmlClassUndecorated>("Undecorated: ");
TestSerialization<MyXmlClassDecorated>("Decorated: ");
Console.ReadLine();
}
private static void TestSerialization<T>(string lineComment) where T : new()
{
XmlDictionary xmlDict = new XmlDictionary();
xmlDict.Add("MyElementName1");
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
using (var writer = XmlDictionaryWriter.CreateBinaryWriter(stream, xmlDict))
{
serializer.WriteObject(writer, new T());
writer.Flush();
Console.WriteLine(lineComment + stream.Length.ToString());
}
}
}
//[DataContract]
public class MyXmlClassUndecorated
{
public MyElementName1[] MyElementName1 { get; set; }
public MyXmlClassUndecorated()
{
MyElementName1 = new MyElementName1[] { new MyElementName1("A A A A A"), new MyElementName1("A A A A A") };
}
}
[DataContract]
public class MyXmlClassDecorated
{
public MyElementName1[] MyElementName1 { get; set; }
public MyXmlClassDecorated()
{
MyElementName1 = new MyElementName1[] { new MyElementName1("A A A A A"), new MyElementName1("A A A A A") };
}
}
[DataContract]
public class MyElementName1
{
[DataMember]
public string Value { get; set; }
public MyElementName1(string value) { Value = value; }
}
}

Categories

Resources