I'm using xml comments to document public as well as internal and private members of my components. I would like to package the generated documentation xml files with component assemblies in order to enable "rich" (e.g. with method, exceptions and parameter descriptions) Visual Studio Intellisense with the end product. The problem with it is that the C# compiler creates documentation entries for everything (including internal classes, methods, private fields of internal enums etc.) and there seems to be no switch to "only public members" mode.
Now I don't want to go over 50 files with XX methods in each and remove all comments for private and internal members. Even if I did that, I probably would not have much success with auto-gen'd resource files, because these strongly-typed resource classes are automatically commented and non-public.
My question is: is there some option/flag that I'm overlooking? If no, are there some tools that could help separate public members from the rest (before I start to code one)?
SandCastle Help File Builder has an option to recreate the xml files containing only the configured access modes for methods, properties, etc...
The only "downside" is that you'll have to generate a documentation.
EDIT
Since it's been a long time ago I forgot that I added a "component" to SHFB to generate the XML.
The good news is that this component is included in SHFB.
You have to add the "Intellisense Component" to the SHFB project. It will then generate the XML according to the configured SHFB project.
For more info: Intellisense Component in SHFB
There is a tool inside the eazfuscator that can remove non-public documentation. You can see an example here
I've given this some thought and I decided to change the way I go about solving this particular problem. Instead of finding type/member within the assembly trying to parse the XML documentation notation. I decided to simply build a string set (of XML documentation notation) for the public API that can then be used to tes wheter a member isn't public.
It's really simple. Send an assembly to the XmlDocumentationStringSet and it will build a string set of the public API and delete the elements that aren't public.
static void Main(string[] args)
{
var el = XElement.Load("ConsoleApplication18.XML");
// obviously, improve this if necessary (might not work like this if DLL isn't already loaded)
// you can use file paths
var assemblyName = el.Descendants("assembly").FirstOrDefault();
var assembly = Assembly.ReflectionOnlyLoad(assemblyName.Value);
var stringSet = new XmlDocumentationStringSet(assembly);
foreach (var member in el.Descendants("member").ToList()) // .ToList enables removing while traversing
{
var attr = member.Attribute("name");
if (attr == null)
{
continue;
}
if (!stringSet.Contains(attr.Value))
{
member.Remove();
}
}
el.Save("ConsoleApplication18-public.XML");
}
And here's the class that builds the XML documentation names (it's a bit large but I thought I post the entire source here anyway):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace ConsoleApplication18
{
public class XmlDocumentationStringSet : IEnumerable<string>
{
private HashSet<string> stringSet = new HashSet<string>(StringComparer.Ordinal);
public XmlDocumentationStringSet(Assembly assembly)
{
AddRange(assembly.GetExportedTypes());
}
public bool Contains(string name)
{
return stringSet.Contains(name);
}
/// <summary>
/// Heelloasdasdasd
/// </summary>
/// <param name="types"></param>
public void AddRange(IEnumerable<Type> types)
{
foreach (var type in types)
{
Add(type);
}
}
public void Add(Type type)
{
// Public API only
if (!type.IsVisible)
{
return;
}
var members = type.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
foreach (var member in members)
{
Add(type, member);
}
}
StringBuilder sb = new StringBuilder();
private void Add(Type type, MemberInfo member)
{
Type nestedType = null;
sb.Length = 0;
switch (member.MemberType)
{
case MemberTypes.Constructor:
sb.Append("M:");
AppendConstructor(sb, (ConstructorInfo)member);
break;
case MemberTypes.Event:
sb.Append("E:");
AppendEvent(sb, (EventInfo)member);
break;
case MemberTypes.Field:
sb.Append("F:");
AppendField(sb, (FieldInfo)member);
break;
case MemberTypes.Method:
sb.Append("M:");
AppendMethod(sb, (MethodInfo)member);
break;
case MemberTypes.NestedType:
nestedType = (Type)member;
if (IsVisible(nestedType))
{
sb.Append("T:");
AppendNestedType(sb, (Type)member);
}
break;
case MemberTypes.Property:
sb.Append("P:");
AppendProperty(sb, (PropertyInfo)member);
break;
}
if (sb.Length > 0)
{
stringSet.Add(sb.ToString());
}
if (nestedType != null)
{
Add(nestedType);
}
}
private bool IsVisible(Type nestedType)
{
return nestedType.IsVisible;
}
private void AppendProperty(StringBuilder sb, PropertyInfo propertyInfo)
{
if (!IsVisible(propertyInfo))
{
sb.Length = 0;
return;
}
AppendType(sb, propertyInfo.DeclaringType);
sb.Append('.').Append(propertyInfo.Name);
}
private bool IsVisible(PropertyInfo propertyInfo)
{
var getter = propertyInfo.GetGetMethod();
var setter = propertyInfo.GetSetMethod();
return (getter != null && IsVisible(getter)) || (setter != null && IsVisible(setter));
}
private void AppendNestedType(StringBuilder sb, Type type)
{
AppendType(sb, type.DeclaringType);
}
private void AppendMethod(StringBuilder sb, MethodInfo methodInfo)
{
if (!IsVisible(methodInfo) || (methodInfo.IsHideBySig && methodInfo.IsSpecialName))
{
sb.Length = 0;
return;
}
AppendType(sb, methodInfo.DeclaringType);
sb.Append('.').Append(methodInfo.Name);
AppendParameters(sb, methodInfo.GetParameters());
}
private bool IsVisible(MethodInfo methodInfo)
{
return methodInfo.IsFamily || methodInfo.IsPublic;
}
private void AppendParameters(StringBuilder sb, ParameterInfo[] parameterInfo)
{
if (parameterInfo.Length == 0)
{
return;
}
sb.Append('(');
for (int i = 0; i < parameterInfo.Length; i++)
{
if (i > 0)
{
sb.Append(',');
}
var p = parameterInfo[i];
AppendType(sb, p.ParameterType);
}
sb.Append(')');
}
private void AppendField(StringBuilder sb, FieldInfo fieldInfo)
{
if (!IsVisible(fieldInfo))
{
sb.Length = 0;
return;
}
AppendType(sb, fieldInfo.DeclaringType);
sb.Append('.').Append(fieldInfo.Name);
}
private bool IsVisible(FieldInfo fieldInfo)
{
return fieldInfo.IsFamily || fieldInfo.IsPublic;
}
private void AppendEvent(StringBuilder sb, EventInfo eventInfo)
{
if (!IsVisible(eventInfo))
{
sb.Length = 0;
return;
}
AppendType(sb, eventInfo.DeclaringType);
sb.Append('.').Append(eventInfo.Name);
}
private bool IsVisible(EventInfo eventInfo)
{
return true; // hu?
}
private void AppendConstructor(StringBuilder sb, ConstructorInfo constructorInfo)
{
if (!IsVisible(constructorInfo))
{
sb.Length = 0;
return;
}
AppendType(sb, constructorInfo.DeclaringType);
sb.Append('.').Append("#ctor");
AppendParameters(sb, constructorInfo.GetParameters());
}
private bool IsVisible(ConstructorInfo constructorInfo)
{
return constructorInfo.IsFamily || constructorInfo.IsPublic;
}
private void AppendType(StringBuilder sb, Type type)
{
if (type.DeclaringType != null)
{
AppendType(sb, type.DeclaringType);
sb.Append('.');
}
else if (!string.IsNullOrEmpty(type.Namespace))
{
sb.Append(type.Namespace);
sb.Append('.');
}
sb.Append(type.Name);
if (type.IsGenericType && !type.IsGenericTypeDefinition)
{
// Remove "`1" suffix from type name
while (char.IsDigit(sb[sb.Length - 1]))
sb.Length--;
sb.Length--;
{
var args = type.GetGenericArguments();
sb.Append('{');
for (int i = 0; i < args.Length; i++)
{
if (i > 0)
{
sb.Append(',');
}
AppendType(sb, args[i]);
}
sb.Append('}');
}
}
}
public IEnumerator<string> GetEnumerator()
{
return stringSet.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
Oh, and I haven't figured out how to handle events yet, they are always visible in this example.
#John Leidegren
I've got the same requirement, and I've found the answer to the missing bit of your code. An Event has 2 methods, Add and Remove, and is considered public if either of them is public. So it would be something like:
private bool IsVisible(EventInfo eventInfo)
{
return eventInfo.GetAddMethod(false) != null
|| eventInfo.GetRemoveMethod(false) != null;
}
although I can't think of any reason why one would be public and not the other.
What tool are you using to generate the documentation? I use Sandcastle and that gives you the option to select members to include by accessability.
In general it seems expected that the XML would contain all info that could be needed and its up to the processing tool to select from it what is needed.
I was facing the same problem. SHFB is slow as hell and since we have another documentation code-base we did not need it to generate the documentation for us.
I ended up using XMLStarlet plus a separate namespace for internal classes. For example, all my internal classes would reside in MyCompany.MyProduct.Internal. Then I can use one simple command
xml ed -L -d "//member[contains(#name, 'MyCompany.MyProduct.Internal')]" MyProduct.xml
to cleanse the XML. This of course is not bullet-proof -- it does not cover internal members in public classes, and it does require some discipline to remember to put internal classes into internal. But this is the cleanest and least intrusive method which works for me. It is also a standalone EXE file easily checked into build server, no sweat.
Related
I am writing a binary PowerShell module that, much like the ActiveDirectory module, will have a number of cmdlets and types that can potentially return more than the default set of properties - and those properties (100+) are dependent on what the user requests. Just like the AD module, I would like to return the type with the properties that were requested so that it is transparent to the user i.e. not one huge class with lots of empty properties that they haven't requested.
I was looking at the documentation for the ActiveDirectory module and I noticed that things like ADUser and ADComputer ultimately are inherited from ADPropertyCollection which I assume has the properties required in its InnerDictionary after whatever searches it does.
This did not really explain how PowerShell presents the AD types flexibly i.e.
(Get-ADUser -Identity some.user).GetType()
(Get-ADUser -Identity some.user -Properties *).GetType()
# Both return a type of ADUser, despite have drastically different amounts of properties.
This was until I looked at the Types ps1xml file for the AD module, which presents something like this:
<Types>
<!-- other types -->
<Type>
<Name>Microsoft.ActiveDirectory.Management.ADUser</Name>
<TypeAdapter>
<TypeName>Microsoft.ActiveDirectory.Management.ADEntityAdapter</TypeName>
</TypeAdapter>
</Type>
</Types>
So I guess the source of the "magic" properties is ADEntityAdapter which is inherited from the abstract class PSPropertyAdapter.
The issue I have is now I am not sure how to implement it and there are not any easily searchable examples of it being implemented. I appreciate its a real edge case. I have had a small attempt at a very rough implementation below, please ignore any code faux pas - I will of course not actually write the code like below. I just wanted to at least try and show I have thought about this.
using System.Collections.Generic;
namespace Sample
{
public class PropertyCollection
{
public string Id { get; set; }
public Dictionary<string, object> Attributes { get; set; }
public PropertyCollection()
{
this.Id = "SampleID";
this.Attributes = new Dictionary<string, object>();
for (var i = 0; i < 10; i++)
{
this.Attributes.Add("KeyAttribute" + i, i);
}
for (var i = 10; i < 100; i++)
{
this.Attributes.Add("OtherAttribute" + i, i);
}
}
}
}
namespace Sample
{
public class ReturnedClass : PropertyCollection
{
public string SomeName { get; set; }
public ReturnedClass() : base() { }
}
}
using System;
using System.Collections.ObjectModel;
using System.Management.Automation;
namespace Sample
{
public class PropertyEntityAdapter : PSPropertyAdapter
{
public override Collection<PSAdaptedProperty> GetProperties(object baseObject)
{
PropertyCollection pc = baseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("Some Exception");
}
Collection<PSAdaptedProperty> collection = new Collection<PSAdaptedProperty>();
foreach (string name in pc.Attributes.Keys)
{
collection.Add(new PSAdaptedProperty(name, null));
}
return collection;
}
public override PSAdaptedProperty GetProperty(object baseObject, string propertyName)
{
PropertyCollection pc = baseObject as PropertyCollection;
if (pc.Attributes.TryGetValue(propertyName, out object pcValue))
{
return new PSAdaptedProperty(propertyName, pcValue);
}
throw new Exception("Prop not found");
}
public override string GetPropertyTypeName(PSAdaptedProperty adaptedProperty)
{
if (adaptedProperty == null)
{
throw new Exception("prop null");
}
PropertyCollection pc = adaptedProperty.BaseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("not found");
}
if (pc.Attributes.TryGetValue(adaptedProperty.Name, out object pcValue))
{
return pcValue.GetType().FullName;
}
return pc.GetType().FullName;
}
public override object GetPropertyValue(PSAdaptedProperty adaptedProperty)
{
if (adaptedProperty?.BaseObject == null || adaptedProperty?.Name == null)
{
throw new Exception("prop null");
}
PropertyCollection pc = adaptedProperty.BaseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("not found");
}
if (pc.Attributes.TryGetValue(adaptedProperty.Name, out object pcValue))
{
return pcValue;
}
return null;
}
public override bool IsGettable(PSAdaptedProperty adaptedProperty)
{
if (adaptedProperty == null)
{
throw new Exception("prop null");
}
PropertyCollection pc = adaptedProperty.BaseObject as PropertyCollection;
if (pc == null)
{
throw new Exception("not found");
}
if (pc.Attributes.ContainsKey(adaptedProperty.Name))
{
return true;
}
return false;
}
public override bool IsSettable(PSAdaptedProperty adaptedProperty)
{
return false;
}
public override void SetPropertyValue(PSAdaptedProperty adaptedProperty, object value)
{
throw new System.NotImplementedException();
}
}
}
** I have purposely not added any code for the Set* methods to reduce the code required here. If the above seems right, then I am sure I know what to do.
Has anyone done this before? Are there any very basic examples out there? Am I on the right path, or am I misunderstanding? Thanks in advance.
If anyone comes comes looking for this and was interested in doing something similar, then my question is actually more or less how it is done. I hope it is of use or interest to others working on binary modules.
One thing I did find were there were some additional properties that were passed through the GetProperty method that seemed to be from the PowerShell engine itself i.e. PSComputerName, so there was no need to throw an error here, just return a new PSAdaptedProperty with a null object.
public override PSAdaptedProperty GetProperty(object baseObject, string propertyName)
{
PropertyCollection pc = baseObject as PropertyCollection;
if (pc.Attributes.TryGetValue(propertyName, out object pcValue))
{
return new PSAdaptedProperty(propertyName, pcValue);
}
return new PSAdaptedProperty(propertyName, null);
}
You can then either load the Types data via your module manifest or in the console using Update-Typedata -PrependPath path_to_file\file_name.
All the properties in the dictionary are returned to give the appearance that they are full properties of the class.
How to define generic class for mapping muti-class?
I asked the question before,but i want like this
public class BaseRepositoryService<T1,T2,T3...Tn> where T:class
{
maplestory2Context mc = new maplestory2Context();
public T AddEntity(params object [] args)
{
for (int i = 0; i < args.Length; i++)
{
object obj = args[i];
}
}
}
but i am not sure the how many params I would use. thanks.
note:i changed my code below,but it is not convenience.
public class BaseRepositoryService<T> where T:class
{
maplestory2Context mc = new maplestory2Context();
public T AddEntity(T entity,int size)
{
if (size == 2)
{
}
return null;
}
}
public class BaseRepositoryService<T,T2>
where T : class
where T2:class
{
maplestory2Context mc = new maplestory2Context();
public T AddEntity(T entity, int size)
{
if (size == 2)
{
}
return null;
}
}
Answer to this question is you can't.
You need to define n no of classes for that even Microsoft has defined Func delegates 15 or 16 times. You can refer to msdn documentation for confirmation.
If you don't want write those classes manually you may opt for t4 templates as code generator.
I'm writing a ReSharper 7.1 Generator plugin and need to get a list of all types declared in the current project (classes, interfaces and structs - IDeclaredType-s) for the GeneratorProviderBase<CSharpGeneratorContext>.Populate method.
With regular reflection it would be as simple as Assembly.GetTypes(), but here it's proven to be quite a challenge. Is there a way to do this?
I've searched high and low, the docs and samples didn't help, then looked through every *Extensions and *Util class, but couldn't find anything useful...
I managed to do what I needed, but I'm not really sure if it's the right/best approach:
[GeneratorElementProvider("MyGeneratorProvider", typeof(CSharpLanguage))]
public class MyGeneratorProvider : GeneratorProviderBase<CSharpGeneratorContext>
{
public override double Priority
{
get { return 0; }
}
public override void Populate(CSharpGeneratorContext context)
{
var projectCsFiles = GetAllCSharpFilesInProject(context.PsiModule);
var declarations = projectCsFiles.SelectMany(GetDeclarationsFromCSharpFile).ToList();
context.ProvidedElements.AddRange(declarations.Select(d => new GeneratorDeclarationElement(d)));
}
private static IEnumerable<ICSharpFile> GetAllCSharpFilesInProject(IPsiModule projectModule)
{
PsiManager psiManager = projectModule.GetPsiServices().PsiManager;
return projectModule.SourceFiles.SelectMany(f => psiManager.GetPsiFiles<CSharpLanguage>(f).OfType<ICSharpFile>());
}
private static IEnumerable<ITypeDeclaration> GetDeclarationsFromCSharpFile(ICSharpFile file)
{
return file.NamespaceDeclarationNodes.SelectMany(GetDeclarationsFromCSharpNamespace);
}
private static IEnumerable<ITypeDeclaration> GetDeclarationsFromCSharpNamespace(ICSharpNamespaceDeclaration namespaceDeclaration)
{
foreach (var namespaceChild in namespaceDeclaration.Body.Children())
{
var classDeclaration = namespaceChild as IClassDeclaration;
if (classDeclaration != null)
{
yield return classDeclaration;
}
else
{
var childNamespace = namespaceChild as ICSharpNamespaceDeclaration;
if (childNamespace != null)
{
foreach (var declaration in GetDeclarationsFromCSharpNamespace(childNamespace))
{
yield return declaration;
}
}
}
}
}
}
Any comments or simpler (maybe even built-in) ways of doing this?
Here's what I came up with using the cache. It still doesn't seem right, but I expect it to be better than the previous approach. This is still called from a GeneratorProvider.Populate:
public static IEnumerable<ICSharpTypeDeclaration> GetAllPublicTypeDeclarations(this IPsiModule module)
{
var declarationCache = module.GetPsiServices().CacheManager.GetDeclarationsCache(module, false, true);
var declarations = new List<ICSharpTypeDeclaration>();
foreach (var shortName in declarationCache.GetAllShortNames())
{
var declaredElements = declarationCache.GetElementsByShortName(shortName).OfType<ITypeElement>().Where(e =>
{
var elementType = e.GetElementType();
return elementType == CLRDeclaredElementType.CLASS || elementType == CLRDeclaredElementType.INTERFACE || elementType == CLRDeclaredElementType.ENUM;
});
foreach (ITypeElement declaredElement in declaredElements)
{
var declaration = declaredElement.GetDeclarations().OfType<ICSharpTypeDeclaration>().FirstOrDefault(d => d.GetAccessRights() == AccessRights.PUBLIC);
if (declaration != null)
{
declarations.Add(declaration);
}
}
}
return declarations;
}
Note: The results won't be the same as in the first answer, cause I've changed some restrictions. Also, in both cases, I'm not concerned with partial classes.
I need help asking my program a series of questions.
For example:
I may say "hi computer" and I want my computer to respond saying "Hi, Sir. How are you?" Then my computer would say "Fine and yourself?" and my computer would say something else.
As of now I'm using Case statements. An example of my code below:
//Kindness
case "thank you":
case "thank you jarvis":
case "thanks":
case "thanks jarvis":
if (ranNum <= 3) { QEvent = ""; JARVIS.Speak("You're Welcome Sir"); }
else if (ranNum <= 6) { QEvent = ""; JARVIS.Speak("Anytime"); }
else if (ranNum <= 10) { QEvent = ""; JARVIS.Speak("No problem boss"); }
break;
An approach I've had good success with is to create 'Contexts', which are (nested) collections of responses and scripts. When you find a matching Context, you push that Context onto a stack, and start looking for responses in the inner Contexts. If no response matches the current set of contexts, you pop the stack and retry. If the stack is empty, you generate a default "I don't understand" response.
An interesting implementation of this could be based on the answers to this question, particularly this answer, which nicely maps the response/action pairs.
A factory pattern is what you need.
The factory simply reflects on all the methods in MySpeechMethods, looks for ones with SpeechAttributes and sends back the MethodInfo to invoke.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MyApp.SpeechMethods;
namespace MyApp
{
class Program
{
static void Main(string[] args)
{
var methods = new MySpeechMethods();
MethodInfo myMethod;
myMethod = SpeechFactory.GetSpeechMethod("Thank you");
myMethod.Invoke(methods, null);
myMethod = SpeechFactory.GetSpeechMethod("Say something funny");
myMethod.Invoke(methods, null);
myMethod = SpeechFactory.GetSpeechMethod("I said funny dammit!");
myMethod.Invoke(methods, null);
}
}
public static class SpeechFactory
{
private static Dictionary<string, MethodInfo> speechMethods = new Dictionary<string, MethodInfo>();
public static MethodInfo GetSpeechMethod(string speechText)
{
MethodInfo methodInfo;
var mySpeechMethods = new MySpeechMethods();
if (speechMethods.Count == 0)
{
var methodNames =
typeof (MySpeechMethods).GetMethods(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance);
var speechAttributeMethods = methodNames.Where(y => y.GetCustomAttributes().OfType<SpeechAttribute>().Any());
foreach (var speechAttributeMethod in speechAttributeMethods)
{
foreach (var attribute in speechAttributeMethod.GetCustomAttributes(true))
{
speechMethods.Add(((SpeechAttribute)attribute).SpeechValue, speechAttributeMethod);
}
}
methodInfo = speechMethods[speechText];
}
else
{
methodInfo = speechMethods[speechText];
}
return methodInfo;
}
}
}
namespace MyApp.SpeechMethods
{
public class MySpeechMethods
{
[Speech("Thank you")]
[Speech("Thank you Jarvis")]
[Speech("Thanks")]
public void YourWelcome()
{
JARVIS.Speak("You're Welcome Sir");
}
[Speech("Say something funny")]
public void SayFunny()
{
JARVIS.Speak("A priest, a rabbi and a cabbage walk into a bar");
}
[Speech("I said funny dammit!")]
public void TryFunnyAgain()
{
JARVIS.Speak("My apologies sir.");
}
}
[System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = true)]
public class SpeechAttribute : System.Attribute
{
public string SpeechValue { get; set; }
public SpeechAttribute(string textValue)
{
this.SpeechValue = textValue;
}
}
}
I'm working in c# with several workspaces that have one specific class which his always the same in each workspace.
I would like to be able have a copy of this class to be able to work with it without dealing with namespaces differences.
example :
namespace1 {
class class1{
public class2;
}
class class2{
public string;
}
}
namespace2 {
class class1{
public class2;
}
class class2{
public string;
}
}
In my copied Class I've got a function to copy all data's to one of the namespace's class.
It's working if i only have c# standard types. I got exeption ( "Object does not match target type." ) as soon as I'm dealing with class2 object (which is also from different namespaces)
public Object toNamespaceClass(Object namespaceClass)
{
try
{
Type fromType = this.GetType();
Type toType = namespaceClass.GetType();
PropertyInfo[] fromProps = fromType.GetProperties();
PropertyInfo[] toProps = toType.GetProperties();
for (int i = 0; i < fromProps.Length; i++)
{
PropertyInfo fromProp = fromProps[i];
PropertyInfo toProp = toType.GetProperty(fromProp.Name);
if (toProp != null)
{
toProp.SetValue(this, fromProp.GetValue(namespaceClass, null), null);
}
}
}
catch (Exception ex)
{
}
return namespaceClass;
}
Anyone do have any idea of how to deal with this kind of "recursivity reflection".
I hope eveything is understandable.
Thanks, Bye!
Edit :
I think i got it solved (at least in my mind), I'll try the solution back at work tomorrow. Taking my function out of my class and using it recursively if a property is not a standard type is maybe the solution.
BinaryFormatter does not work in .Net 4.5 as it remembers from what type of class the instance was created. But with JSON format, it does not. JSON serializer is implemented by Microsoft in DataContractJosnSerializer.
This works:
public static T2 DeepClone<T1, T2>(T1 obj)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T1));
DataContractJsonSerializer deserializer = new DataContractJsonSerializer(typeof(T2));
using (var ms = new MemoryStream())
{
serializer.WriteObject(ms, obj);
ms.Position = 0;
return (T2)deserializer.ReadObject(ms);
}
}
and uses as follows:
var b = DeepClone<A, B>(a);
I had the similar problem. I got to use similar classes but different in terms of namespace only. As a quick solution I performed below steps and it works.
Serialize source class into XML.
In SerializedXML replace source namespace with the target one.
DeSerialize with target type.
I know there is performance overhead with above way but it is quick to implement and error free.
I got it solved , just to let you know how I did it :
This solution is sot perfect because it handle only 1 dimensions array not more.
public static Object CopyObject(Object from , Object to)
{
try
{
Type fromType = from.GetType();
Type toType = to.GetType();
PropertyInfo[] fromProps = fromType.GetProperties();
PropertyInfo[] toProps = toType.GetProperties();
for (int i = 0; i < fromProps.Length; i++)
{
PropertyInfo fromProp = fromProps[i];
PropertyInfo toProp = toType.GetProperty(fromProp.Name);
if (toProp != null)
{
if (toProp.PropertyType.Module.ScopeName != "CommonLanguageRuntimeLibrary")
{
if (!toProp.PropertyType.IsArray)
{
ConstructorInfo ci = toProp.PropertyType.GetConstructor(new Type[0]);
if (ci != null)
{
toProp.SetValue(to, ci.Invoke(null), null);
toProp.SetValue(to, gestionRefelexion.CopyObject(fromProp.GetValue(from, null), toProp.GetValue(to, null)), null);
}
}
else
{
Type typeToArray = toProp.PropertyType.GetElementType();
Array fromArray = fromProp.GetValue(from, null) as Array;
toProp.SetValue(to, copyArray(fromArray, typeToArray), null);
}
}
else
{
toProp.SetValue(to, fromProp.GetValue(from, null), null);
}
}
}
}
catch (Exception ex)
{
}
return to;
}
public static Array copyArray(Array from, Type toType)
{
Array toArray =null;
if (from != null)
{
toArray= Array.CreateInstance(toType, from.Length);
for (int i = 0; i < from.Length; i++)
{
ConstructorInfo ci = toType.GetConstructor(new Type[0]);
if (ci != null)
{
toArray.SetValue(ci.Invoke(null), i);
toArray.SetValue(gestionRefelexion.CopyObject(from.GetValue(i), toArray.GetValue(i)), i);
}
}
}
return toArray;
}
Hope this can help some people.
Thanks for helping everyone.
Cheers
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);
}
}
from here
Two identical or similar objects from different namespaces ?
You have this:
namespace Cars
{
public class car {
public string Name;
public void Start() { ... }
}
}
namespace Planes
{
public class plane {
public string Name;
public void Fly() { ... }
}
}
Time to apply some class inheritance:
namespace Vehicles
{
public class vehicle
{
public string Name;
} // class
} // namespace
using Vehicles;
namespace Cars
{
public class car: vehicle
{
public string Name;
public void Start() { ... }
} // class
} // namespace
using Vehicles;
namespace Planes
{
public class plane: vehicle
{
public void Fly() { ... }
}
}
And to copy, there is a copy method or constructor, but, I prefer a custom one:
namespace Vehicles
{
public class vehicle {
public string Name;
public virtual CopyFrom (vehicle Source)
{
this.Name = Source.Name;
// other fields
}
} // class
} // namespace
Cheers.
You either need to refactor all of your duplicate classes into a single shared class or implement a common interface that all of your various classes implement. If you really can't modify the underlying types, create a subclass for each that implements your common interface.
Yes, you can do it with reflection... but you really shouldn't because you end up with brittle, error prone, code.
This problem can be elegantly solves using Protocol Buffers because Protocol Buffers do not hold any metadata about the type they serialize. Two classes with identical fields & properties serialize to the exact same bits.
Here's a little function that will change from O the original type to C the copy type
static public C DeepCopyChangingNamespace<O,C>(O original)
{
using (MemoryStream ms = new MemoryStream())
{
Serializer.Serialize(ms, original);
ms.Position = 0;
C c = Serializer.Deserialize<C>(ms);
return c;
}
}
usage would be
namespace1.class1 orig = new namespace1.class1();
namespace2.class1 copy =
DeepCopyChangingNamespace<namespace1.class1, namespace2.class1>(orig);