How can I cast a class object to a list? - c#

I'm fairly new to C# and I'm having trouble converting an object to a List<T>. I've been receiving the error "Cannot implicitly convert type Attachment to System.Collections.Generic.List<Attachment>. There are lots of posts about similar errors that I've looked over, but I can't seem to figure out what I'm missing.
My core object looks like:
public class Attachment
{
public Attachment() { }
...
}
It's being called in another class' constructor like so:
public class MyClass
{
...
public List<Attachment> attachments { get; set; };
...
public MyClass(JObject jobj)
{
...
//Attachments
if (jobj["attachments"] != null)
{
attachments = (Attachment)jobj.Value<Attachment>("attachments");
}
}
}
The error is occurring in the last line of code where I'm trying to cast my Attachment object to the List<attachments>. I understand what the message is saying, but everything I've tried doesn't work.

You are setting a List<T> to a T.
attachments = (Attachment)jobj.Value<Attachment>("attachments");
Instead, you probably want to Add it. But don't forget to instantiate the list first.
attachments = new List<Attachment>();
attachments.Add((Attachment)jobj.Value<Attachment>("attachments"));
Think about it in terms that don't involve generics. Say I have an int x and I set it to a string constant.
int x = "test";
What would that mean? Those are complete different types. That's kind of like the conversion you're asking the compiler to perform. The type on the left has to be (a polymorphic parent of or) the type on the right.

Simply use the ToObject method
List<Attachment> attachments = jobj["attachments"].ToObject<List<Attachment>>();

Related

How to initialize a derived class from base class

I'm using C# with the .NET 6 framework. I have a class called
Message and another called TaggedMessage which inherits from Message.
The idea is simple. A function receives an object of type Message and then adds several Tags to it and returns it as a TaggedMessage. A list of TaggedMessage objects is later displayed in a table. For databinding to remain nice and easy I want TaggedMessage to not contain nested properties. So it shouldn't hold an instance of Message for example. Instead it should contain all the properties from Message plus additional ones.
So I thought it should inherit from Message. However I cannot find a way to instantiate TaggedMessage from Message unless I specifically assign every column from Message to TaggedMessage in its constructor. Which seems overly difficult and would mean everytime I add a property to Message, I would have to revisit the constructor of TaggedMessage. Exmaple (obviously the real thing is more complex)
public class Message
{
public string MessageID { get; set; } = "5";
public string Subject{ get; set; } = "Test";
}
Public class TaggedMessage : Message
{
public string MyTag { get; set; }
}
Message m = new Message();
TaggedMessage t = TaggedMessage;
t = (TaggedMessage)m; //This ovbiously doesn't work
t.Tag = "Nature";
Now the casting doesn't work because I'm casting a base class in a derived class. But then, how to I get the values from m into t? Let's assume m has 50 properties and they could change in the future. How can get an object t that has all the values m had, but with extra tags added? There must be a more elegant way than assigning all 50 properties in the constructor!? I feel like I'm missing a simple solution here.
Message object cannot be cast to a TaggedMessage type.
What are you looking for is called mapping and there are a lot of libraries for that, including but not limited to Automapper, Mapster or ExpressMapper for example.
AutoMapper:
static void Main()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Message, TaggedMessage>()
.IncludeAllDerived();
});
var mapper = config.CreateMapper();
var m = new Message() { MessageID = "SomeMessageID", Subject = "SomeSubject" };
var t = mapper.Map<TaggedMessage>(m);
t.MyTag = "MyTag";
Console.WriteLine(t.MessageID);
Console.WriteLine(t.Subject);
Console.WriteLine(t.MyTag);
}
There are certain ways to do what you intend to do without bothering writing the mappings manually.
One of them is using a library that does it for you, like AutoMapper as Viachaslau suggests.
Another can can be serializing and deserializing the object:
var message = new Message();
var str = System.Text.Json.JsonSerializer.Serialize(message);
var taggedMessage = System.Text.Json.JsonSerializer.Deserialize<TaggedMessage>(str);
taggedMessage.MyTag = "Nature";
Both alternatives to writing that code in the constructor have their cons and can be expensive in their own ways depending on the use case, so it's not about whether you can avoid it, but about whether you should do it or not!

How can I convert a list of Models to another list of Models that inherits from it?

I have a piece of code that retrieves a long list of members via a library.
public List<InstinctGuildMember> PrepareGuildMembersList()
{
List<GuildMember> warcraftGuildMembers = _warcraftClient.GetGuildRosterAsync("dragonmaw", "instinct", "profile-eu")
.Result.Value.Members.OrderBy(members => members.Rank)
.ThenBy(members => members.Character.Name)
.ToList();
// We cast a new List from our old List.
List<InstinctGuildMember> instinctGuildMembers = new List<InstinctGuildMember>(warcraftGuildMembers.Cast<InstinctGuildMember>());
return instinctGuildMembers;
}
namespace guild_instinct.Models.GuildData
{
public class InstinctGuildMember : GuildMember
{
public string RankName { get; set; }
}
}
The reason I want to convert to my own Model is because the provided Model does not have all the information I need. A friend of mine suggested making my own Model and inheriting the model from the library.
I found this solution on a related StackOverflow question. However, I couldn't get it to work as I kept receiving the error following error
System.InvalidCastException: 'Unable to cast object of type 'ArgentPonyWarcraftClient.GuildMember' to type 'guild_instinct.Models.GuildData.InstinctGuildMember'.'
It's likely that I just lack the knowledge so far to do it correctly and the answer is staring me in the face.
I am clueless on how to continue. What knowledge should I delve into to further my progress? Am I in the right direction but do I lack some fundamentals to make it work? I hope my question is concise enough to warrant help.
You cannot cast the original list that way, since a GuildMember instance is not a InstinctGuidMember.
You can use projection, i.e. project the original list onto a new list of a different type:
var instinctGuildMembers = warcraftGuildMembers.Select(x => new InstinctGuildMember(x, rank)).ToList();
To have your model contain data from GuildMember and additional data, there are 2 ways:
Composition - Make your model have a GuildMember instance.
class InstinctGuildMember {
private GuildMember _guildMember;
private string _rankName;
public InstinctGuildMember(GuildMember guildMember, string rankName)
{
_guildMember = guildMember;
_rankName = rankName;
}
}
Inheritance - Make your model be a GuildMember by inheriting the type.
class InstinctGuildMember : GuildMember {
private string _rankName;
//the constructor here depends on what constructors are available on GuildMember.
}
You cannot cast but you can convert.
List<InstinctGuildMember> instinctGuildMembers =
warcraftGuildMembers.ConvertAll<InstinctGuildMember>(ConvertFunction);
And for ConvertFunction :
static InstinctGuildMember ConvertFunction(GuildMember x)
{
InstinctGuildMember y = new InstinctGuildMember();
y.prop1 = x.prop1;
y.prop2 = x.prop2;
y.RankName = "";
return y;
}

cast an array to an array of custom data type

I am learning C# and so far, its been a tough ride. I might be misunderstanding somethings but here it is. I have the following two interfaces:
internal class PersonAttributes
{
public int SID;
public int isCurrent;
}
internal class SavedPeople
{
public int? maxAllowed;
public PersonAttributes[] people;
}
SavedPeople is the object that will be returned at last and the people attribute is an array of people with SID and isCurrent field for each person. Now i have the following piece of code:
var people = new SavedPeople();
var currentPeople = JObject.Parse(foundPeople);
people.maxAllowed = (int)currentPeople["maxAllowed"];
settings.people = currentPeople["people"].ToArray<PersonAttributes>();
currentPeople is an object that contains people and maxAllowed field. the "people" field is guranteed to contain the same fields as PersonAttributes. I want to cast its contents to PersonAttributes so when I loop settings.people, I get its properties right to set some conditions. The above fails with the following error:
'JToken' does not contain a definition for 'ToArray' and the best extension method overload 'Enumerable.ToArray' requires a receiver of type 'IEnumerable'
Can you just deserialise the json to the concrete type?
JsonConvert.DeserializeObject<SavedPeople>(foundPeople);
Use ToObject<List<T>>()
j["people"].ToObject<List<PersonAttributes>>();
BTW. If you want to parse arrays directly use JArray.Parse.

Solution for CA2227 or better approach?

I'm only using Code Analysis for cleaning, organizing and ensuring these changes are globally performed for all instances of a particular warning. I'm down to the final, and it's CA2227.
CA2227 Collection properties should be read only Change '' to be
read-only by removing the property setter.
Note this is for mapping of EDI documents. These classes are to represent a whole or part of an EDI document.
public class PO1Loop
{
public SegmentTypes.PO1LoopSegmentTypes.PO1 PO1 { get; set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID1> PIDRepeat1 { get; set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID2> PIDRepeat2 { get; set; }
public SegmentTypes.PO1LoopSegmentTypes.PO4 PO4 { get; set; }
/* Max Use: 8 */
public Collection<SegmentTypes.PO1LoopSegmentTypes.ACK> ACKRepeat { get; set; }
}
You can see all of the Collection properties will give me this warning, and there are hundreds of them. When using the above class I instantiate it without any data. Then externally I add the data and set each individual variable through its public accessor. I do not instantiate this class with all the data prepared and passed using a constructor method (IMO for the size these can reach it can easily wreak havoc on the eyes). When complete and all properties are assigned the class as a whole is then used to generate that part of a document it represents.
My question is, for the usage described above, what would be a better approach for setting this up correctly? Do I keep the public accessors and suppress this warning entirely, or is there a entirely different solution that would work?
Here's what MSDN says about the error, and also how you can avoid it.
Here's my take on the issue.
Consider, the following class:
class BigDataClass
{
public List<string> Data { get; set; }
}
This class will throw that exact same issue. Why? Because Collections do not need a setter. Now, we can do anything with that object: assign Data to an arbitrary List<string>, add elements to Data, remove elements from Data, etc. If we remove the setter, we only lose the ability to directly assign to that property.
Consider the following code:
class BigDataClass
{
private List<string> data = new List<string>();
public List<string> Data { get { return data; } } // note, we removed the setter
}
var bigData = new BigDataClass();
bigData.Data.Add("Some String");
This code is perfectly valid and in fact the recommended way to do things. Why? Because the List<string> is a reference to a memory location, that contains the remainder of the data.
Now, the only thing you cannot now do with this, is directly set the Data property. I.e. the following is invalid:
var bigData = new BigDataClass();
bigData.Data = new List<string>();
This is not necessarily a bad thing. You'll notice that on many .NET types this model is used. It's the basics of immutability. You usually do not want direct access to the mutability of Collections, as this can cause some accidental behavior that has strange issues. This is why Microsoft recommends you omit setters.
Example:
var bigData = new BigDataClass();
bigData.Data.Add("Some String");
var l2 = new List<string>();
l2.Add("String 1");
l2.Add("String 2");
bigData.Data = l2;
Console.WriteLine(bigData.Data[0]);
We might be expecting Some String, but we'll get String 1. This also means that you cannot reliably attach events to the Collection in question, so you cannot reliably determine if new values are added or values are removed.
A writable collection property allows a user to replace the collection with a completely different collection.
Essentially, if you only ever need to run the constructor, or assignment, once, then omit the set modifier. You won't need it, direct assignment of collections is against best-practices.
Now, I'm not saying never use a setter on a Collection, sometimes you may need one, but in general you should not use them.
You can always use .AddRange, .Clone, etc. on the Collections, you only lose the ability of direct assignment.
Serialization
Lastly, what do we do if we wish to Serialize or Deserialize a class that contains our Collection without a set? Well, there is always more than one way to do it, the simplest (in my opinion) is to create a property that represents the serialized collection.
Take our BigDataClass for example. If we wished to Serialize, and then Deserialize this class with the following code, the Data property would have no elements.
JavaScriptSerializer jss = new JavaScriptSerializer();
BigDataClass bdc = new BigDataClass();
bdc.Data.Add("Test String");
string serd = jss.Serialize(bdc);
Console.WriteLine(serd);
BigDataClass bdc2 = jss.Deserialize<BigDataClass>(serd);
So, to fix this, we can simply modify our BigDataClass a bit to make it use a new string property for Serialization purposes.
public class BigDataClass
{
private List<string> data = new List<string>();
[ScriptIgnore]
public List<string> Data { get { return data; } } // note, we removed the setter
public string SerializedData { get { JavaScriptSerializer jss = new JavaScriptSerializer(); return jss.Serialize(data); } set { JavaScriptSerializer jss = new JavaScriptSerializer(); data = jss.Deserialize<List<string>>(value); } }
}
Another option is always the DataContractSerializer (which is really a better option, in general.) You can find information about it on this StackOverflow question.
With current VS2019 we can simply do this:
public List<string> Data { get; } = new List<string>();
This satisfies CA2227 and can be serialized/deserialized.
The deserialization works because List<> has an "Add" method, and the serializer knows how to handle a read-only collection property with an Add method (the property is read-only but not the elements) (I use Json.Net, other serializers may behave differently).
Edit:
As pointed out it should be "=" and not "=>" (compiler will prevent you using "=>"). If we used "public List Data => new List();" then it would create a new list every time the property was accessed which is not what we want either.
Edit:
Note that this will NOT work if the type of the property is an interface, such as IList
Edit:
I think the handling of interfaces is determined by the serializer used. The following works perfectly. I'm sure all common serializers know how to handle ICollection. And if you have some custom interface that does not implement ICollection then you should be able to configure the serializer to handle it, but in that case CA2227 probably won't be triggered making it irrelevant here. (As it is a read-only property you have to assign a concrete value within the class so it should always be serializing and de-serializing a non-null value)
public class CA2227TestClass
{
public IList Data { get; } = new List<string>();
}
[TestMethod]
public void CA2227_Serialization()
{
var test = new CA2227TestClass()
{
Data = { "One", "Two", "Three" }
};
var json = JsonConvert.SerializeObject(test);
Assert.AreEqual("{\"Data\":[\"One\",\"Two\",\"Three\"]}", json);
var jsonObject = JsonConvert.DeserializeObject(json, typeof(CA2227TestClass)) as CA2227TestClass;
Assert.IsNotNull(jsonObject);
Assert.AreEqual(3, jsonObject.Data.Count);
Assert.AreEqual("One", jsonObject.Data[0]);
Assert.AreEqual("Two", jsonObject.Data[1]);
Assert.AreEqual("Three", jsonObject.Data[2]);
Assert.AreEqual(typeof(List<string>), jsonObject.Data.GetType());
}
💡 Alternative Solution 💡
In my situation, making the property read-only was not viable because the whole list (as a reference) could change to a new list.
I was able to resolve this warning by changing the properties' setter scope to be internal.
public List<Batch> Batches
{
get { return _Batches; }
internal set { _Batches = value; OnPropertyChanged(nameof(Batches)); }
}
Note one could also use private set...
The hint's (achilleas heal) of this warning seems really pointed to libraries for the documentation says (Bolding mine):
An externally visible writable property is a type that implements
System.Collections.ICollection.
For me it was, "Ok, I won't make it viewable externally...." and internal was fine for the app.
Thanks to #Matthew, #CraigW and #EBrown for helping me understanding the solution for this warning.
public class PO1Loop
{
public SegmentTypes.PO1LoopSegmentTypes.PO1 PO1 { get; set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID1> PIDRepeat1 { get; private set; }
public Collection<SegmentTypes.PO1LoopSegmentTypes.PID2> PIDRepeat2 { get; private set; }
public SegmentTypes.PO1LoopSegmentTypes.PO4 PO4 { get; set; }
/* Max Use: 8 */
public Collection<SegmentTypes.PO1LoopSegmentTypes.ACK> ACKRepeat { get; private set; }
public PO1Loop()
{
PIDRepeat1 = new Collection<SegmentTypes.PO1LoopSegmentTypes.PID1>();
PIDRepeat2 = new Collection<SegmentTypes.PO1LoopSegmentTypes.PID2>();
ACKRepeat = new Collection<SegmentTypes.PO1LoopSegmentTypes.ACK>();
}
}
When wanting to assign data to the collection types use AddRange, Clear or any other variation of method for modifying a collection.
Only while binding DTO, you need to suppress warnings.
otherwise a custom ModelBinder is required custom ModelBinder to bind collections.
quoting the rule documentation:
When to suppress warnings
You can suppress the warning if the property is part of a Data Transfer Object (DTO) class.
Otherwise, do not suppress warnings from this rule.
https://learn.microsoft.com/pt-br/visualstudio/code-quality/ca2227?view=vs-2019
DTOs often require serialization and deserialization. Thus, they are required to be mutable.
Having to create an alternate backing property is a pain.
Simply change the property type from List<string> to IReadOnlyList<string> then this works as expected without CA2227.
The collection is set via the property but you can also cast to List<string> if you wish to append or delete items.
class Holder
{
public IReadOnlyList<string> Col { get; set; } = new List<string>();
}
var list = new List<string> { "One", "Two" };
var holder = new Holder() { Col = list } ;
var json = JsonConvert.SerializeObject(holder);
// output json {"Col":["One","Two"]}
var deserializedHolder = JsonConvert.DeserializeObject<Holder>(json);
I had to fix some of the CA2227 violations, so i had to add the "readonly" keyword to the collection field and then of course, had to remove the setter property. Some code that have used the setter, just created a new collection object which initially was empty. This code sure did not compile so i had to add a SetXxx() method in order to realize the missing setter's functionality. I did it like this:
public void SetXxx(List<string> list)
{
this.theList.Clear();
this.theList.AddRange(list);
}
The code of callers using the setter has been replaced with a call to the method SetXxx().
Instead of creating a complete new list, the existing list now will be cleared and filled with new items from another list, passed in as a parameter. The original list, due to the fact it is readonly and created only once, will always remain.
I believe this is also a good way to avoid that the garbagae collector has to delete old objects that got out of scope and second, to create new collection objects although there is already one.
As an addition to Der Kommissar's excellent answer.
Starting with .NET 5 (C# 9.0) there are init-only properties. These properties are only settable under specific circumstances, see here for reference.
The following example should not raise a warning CA2227, yet still allow for the collection being set during object initialization.
using System.Collections.Generic;
namespace BookStore
{
public class BookModel
{
public ICollection<string> Chapters { get; init; }
}
}
Note that the current version of the .NET SDK still raises a warning when using the built-in analyzer (not the NuGet package). This is a known bug and should be fixed in the future.
To cover all the possible scenarios to resolve CA2227 error:
This covers the Entity relationship mapping when we use Entity Framework.
class Program
{
static void Main(string[] args)
{
ParentClass obj = new ParentClass();
obj.ChildDetails.Clear();
obj.ChildDetails.AddRange();
obj.LstNames.Clear();
obj.LstNames.AddRange();
}
}
public class ChildClass
{ }
public class ParentClass
{
private readonly ICollection<ChildClass> _ChildClass;
public ParentClass()
{
_ChildClass = new HashSet<ChildClass>();
}
public virtual ICollection<ChildClass> ChildDetails => _ChildClass;
public IList<string> LstNames => new List<string>();
}

How to use Json.NET with standard (like) OData service

I try to use Json.NET for consuming an OData (like) service. My data structures have collection properties.
However all those properties are wrapped in a property called 'results' by my service. Even the main query which returns with a collection of the entities are wrapped into a root property called results.
Although I am not an OData expert I think this is pretty much a standard because if I try to send an object graph for update, and omit this 'results' wrapper around say a int collection type property then I got an error message from the server "A collection was found without the 'results' property. In OData, each collection must be represented as a JSON object with a property 'results'"
So I understand the server error message, and I also know how to workaround this. Of course I can mimic this object graph in my C# object model, but after writing the 101st wrapper in my object model it tends to be boring, and I do not want to reinvent the wheel.
My question is there an out of the box solution how to deal with this result property, and make it transparent? If this is a 'standard' I suppose not I am the first who are facing this task.
Thanks in advance
you can just write one and use it everywhere
public class OData<T> where T : IEnumerable
{
public T results { get; set; }
}
public class X
{
public string Prop { get; set; }
public OData<List<int>> List { get; set; }
}
class Program
{
static void Main(string[] args)
{
var x = new X()
{
Prop = "test",
List = new OData<List<int>> {results = new List<int>() {1, 2, 3}}
};
Console.WriteLine(JsonConvert.SerializeObject(x));
}
}

Categories