I'm writing a simple console weather app (OpenWeatherMap) and I would like to validate whether response from OWM contains any null properties. This is my class containing properties:
public class WeatherMain
{
public Coord coord { get; set; }
public List<Weather> weather { get; set; }
public Main main { get; set; }
public Wind wind { get; set; }
public Rain rain { get; set; }
public Snow snow { get; set; }
public Clouds clouds { get; set; }
public Sys sys { get; set; }
[JsonProperty("base")]
public string _base { get; set; }
public int? visibility { get; set; }
public int? timezone { get; set; }
public int? id { get; set; }
public string name { get; set; }
public int? cod { get; set; }
}
As you can see there are also some classed containing their own properties as well.
I would like to check, if any of these is basically a null, so I can inform about missing value in Console. Right now I did some basic check using IFs for WeatherMain properties:
public static string PrepareResponse(WeatherMain input)
{
string cityName, main, visibility, wind, clouds, rain, snow, coord;
if (input.name == null)
cityName = "City name section not found\n";
else
cityName = $"\nCity name: {input.name}\n";
if (input.main == null)
main = "Main section not found\n";
else
{
main = $"Main parameters:\n\tTemperature: {input.main.temp}C\n\t" +
$"Temperature max.: {input.main.temp_max}C\n\tTemperature min.: {input.main.temp_min}C" +
$"\n\tFeels like: {input.main.feels_like}C\n\tPressure: {input.main.pressure}hPA\n\t" +
$"Humidity: {input.main.humidity}%\n";
}
if (input.visibility == null)
visibility = "Visibility section not found\n";
else
visibility = $"Visibility: {input.visibility}m\n";
if (input.wind == null)
wind = "Wind section not found\n";
else
wind = $"Wind:\n\tSpeed: {input.wind.speed}m/s\n\tDirection: {input.wind.deg}deg\n";
if (input.clouds == null)
clouds = "Clouds section not found\n";
else
clouds = $"Clouds: {input.clouds.all}%\n";
if (input.rain == null)
rain = "Rain section not found\n";
else
rain = $"Rain: {input.rain._1h}mm\n";
if (input.snow == null)
snow = "Snow section not found\n";
else
snow = $"Snow: {input.snow._1h}mm\n";
if (input.coord == null)
coord = "Coordinates section not found\n";
else
coord = $"Coordinates:\n\tLatitude: {input.coord.lat}\n\tLongitude: {input.coord.lon}\n";
string outputString = cityName + main + visibility + wind + clouds + rain + snow + coord;
return outputString;
}
I was thinking of putting those properties to a collection and checking them for null one by one and maybe changing the value to string.
I guess there are better ways to do that.
Any ideas?
you can do this by using Reflection namespace:
var weather = new WeatherMain
{
visibility = 2,
timezone = 5
};
Type t = weather.GetType();
Console.WriteLine("Type is: {0}", t.Name);
PropertyInfo[] props = t.GetProperties();
Console.WriteLine("Properties (N = {0}):",
props.Length);
foreach (var prop in props)
if (prop.GetValue(weather) == null)
{
Console.WriteLine(" {0} ({1}) is null", prop.Name,
prop.PropertyType.Name);
}
here is a sample fiddle: https://dotnetfiddle.net/MfV7KD
Using a descriptor class, you can consolidate the value access, the null messsage and the formatted output into one place, and then loop through them.
First, the Parameter class will describe each parameter:
public class Parameter {
public Func<WeatherMain, object> Value;
public string NullName;
public Func<WeatherMain, string> FormatSection;
}
Then, a static List will describe all the parameters you want to test and output:
public static List<Parameter> Parameters = new[] {
new Parameter { Value = w => w.name, NullName = "City name", FormatSection = w => $"City name: {w.name}" },
new Parameter { Value = w => w.main, NullName = "Main",
FormatSection = w => $"Main parameters:\n\tTemperature: {w.main.temp}C\n\t" +
$"Temperature max.: {w.main.temp_max}C\n\tTemperature min.: {w.main.temp_min}C" +
$"\n\tFeels like: {w.main.feels_like}C\n\tPressure: {w.main.pressure}hPA\n\t" +
$"Humidity: {w.main.humidity}%" },
new Parameter { Value = w => w.visibility, NullName = "Visibility", FormatSection = w => $"Visibility: {w.visibility}" },
new Parameter { Value = w => w.wind, NullName = "Wind", FormatSection = w => $"Wind:\n\tSpeed: {w.wind.speed}m/s\n\tDirection: {w.wind.deg}deg" },
new Parameter { Value = w => w.clouds, NullName = "Clouds", FormatSection = w => $"Clouds: {w.clouds.all}%" },
new Parameter { Value = w => w.rain, NullName = "Rain", FormatSection = w => $"Rain: {w.rain._1h}mm" },
new Parameter { Value = w => w.snow, NullName = "Snow", FormatSection = w => $"Snow: {w.snow._1h}mm" },
new Parameter { Value = w => w.coord, NullName = "Coordinates", FormatSection = w => $"Coordinates:\n\tLatitude: {w.coord.lat}\n\tLongitude: {w.coord.lon}" },
}.ToList();
Finally, you can produce the formatted response by looping through the list:
public static string PrepareResponse(WeatherMain input) {
var ans = new List<string>();
foreach (var p in Parameters) {
if (p.Value(input) == null)
ans.Add($"{p.NullName} section not found");
else
ans.Add(p.FormatSection(input));
}
return String.Join("\n", ans);
}
Related
I'm trying to agregate a list of multiple propertys with Linq.
My Second field is a List of Strings + an other List of strings inside.
Here's a sample of my code :
using System;
using System.Collections.Generic;
using System.Linq;
public class RefValueData
{
public int ReferenceId { get; set; }
public int SiteId { get; set; }
public string SiteName { get; set; }
public string Code { get; set; }
public decimal UnitPoints { get; set; }
public List<TranslationData> Texts { get; set; }
}
public class TranslationData
{
public string Text { get; set; }
public List<TranslationValue> Translations { get; set; }
}
public class TranslationValue
{
public string Culture { get; set; }
public string TranslationText { get; set; }
}
public class Program
{
public static void Main()
{
var values = new List<RefValueData>
{
new RefValueData(){
ReferenceId = 4,
Code = "Code",
SiteId = 2,
SiteName = "Paris",
UnitPoints = 50,
Texts = new List<TranslationData>
{
new TranslationData(){
Text = "A",
Translations = new List<TranslationValue>
{
new TranslationValue() { Culture = "FR-fr", TranslationText = "Bonjour" },
new TranslationValue() { Culture = "ES-es", TranslationText = "Hola" },
}
}
}
},
new RefValueData()
{
ReferenceId = 5,
Code = "Code",
SiteId = 4,
SiteName = "Lyon",
UnitPoints = 50,
Texts = new List<TranslationData>
{
new TranslationData(){
Text = "A",
Translations = new List<TranslationValue>
{
new TranslationValue() { Culture = "FR-fr", TranslationText = "Bonjour" },
new TranslationValue() { Culture = "ES-es", TranslationText = "Hola" },
}
}
}
},
new RefValueData()
{
ReferenceId = 6,
Code = "Code",
SiteId = 3,
SiteName = "Paris",
UnitPoints = 52,
Texts = new List<TranslationData>
{
new TranslationData(){
Text = "B",
Translations = new List<TranslationValue>
{
new TranslationValue() { Culture = "FR-fr", TranslationText = "Salut" },
new TranslationValue() { Culture = "ES-es", TranslationText = "Ciao" },
}
}
}
}
};
var values2 = values
.Distinct()
.GroupBy(x => new
{
x.UnitPoints,
x.Texts
})
.Select(x => new
{
x.Key.UnitPoints,
Texts = x.Key.Texts,
Site = x.Select(y=>y.SiteName)
})
.ToList();
Console.WriteLine(values2.Count);
}
}
I want to have only two lines in my values2 list, but everytime it returns me the whole list.
When I only group by Unit Point, it's work great !
I tried to group the first two lines of my list with some custom Linq query but it doesn't work at all...
Any help / advice is much appreciated :) !
EDIT :
I also tried with an override of the Equals methods like this, but I can't make it work :
public class TranslationValue
{
public string Culture { get; set; }
public string TranslationText { get; set; }
public override bool Equals(object obj)
{
var other = obj as TranslationValue;
if (other == null)
{
return false;
}
return Culture == other.Culture && TranslationText == other.TranslationText;
}
public override int GetHashCode()
{
var hashCode = -2095322044;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Culture);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(TranslationText);
return hashCode;
}
}
public class TranslationData
{
public string Text { get; set; }
public List<TranslationValue> Translations { get; set; }
public override bool Equals(object obj)
{
var other = obj as TranslationData;
if (other == null)
{
return false;
}
return Text == other.Text && Translations.SequenceEqual(other.Translations);
}
public override int GetHashCode()
{
var hashCode = -1551681861;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Text);
hashCode = hashCode * -1521134295 + EqualityComparer<List<TranslationValue>>.Default.GetHashCode(Translations);
return hashCode;
}
}
EDIT2 : Here's my 'real' code :
var values = referenceValues.Select(value => new
{
ReferenceId = value.ReferenceId,
SiteId = value.Reference.SiteId ?? -1,
SiteName = value.Reference.Site.Name ?? allSitesName,
Code = value.Code,
UnitPoints = value.UnitPoints,
Texts = // Type: List<TranslationData> , but it will not use the TranslationDataList class that normally work thanks to your help
value.ReferenceValueTexts.Select(text =>
new TranslationData
{
Text = text.Text, // string
Translations = text.TranslationDataValues.Select(translation => // List<TranslationValue>
new TranslationValue {
Culture = translation.Language.StrCulture,
TranslationText = translation.Value
}).ToList()
}).ToList()
}
Julien.
Here's one solution. It works for the sample code that you wrote. But it needs a little work to be robust:
// and also change the declarations in the main method to: new TranslationDataList
public class TranslationDataList : List<TranslationData>
{
public override int GetHashCode()
{
int hash = 13;
// string.GetHashCode() is not reliable. This should be an algorithm that returns the same value for two different lists that contain the same data
foreach (var data in this)
hash = (hash * 7) + data.Text.GetHashCode();
return hash;
}
public override bool Equals(object obj)
{
var other = obj as TranslationDataList;
if (other == null) return false;
if (other.Count != Count) return false;
// write the equality logic here. I don't know if it's ok!
for (int i = 0; i < other.Count; i++)
if (other[i].Text != this[i].Text)
return false;
return true;
}
}
First of all you should add a constructor to the TranslationDataList:
public class TranslationDataList : List<TranslationData>
{
public TranslationDataList(IEnumerable<TranslationData> translationData)
: base(translationData)
{ }
// other members ...
}
Now you can use the TranslationDataList in your query:
var values = referenceValues.Select(value => new
{
ReferenceId = value.ReferenceId,
SiteId = value.Reference.SiteId ?? -1,
SiteName = value.Reference.Site.Name ?? allSitesName,
Code = value.Code,
UnitPoints = value.UnitPoints,
Texts = new TranslationDataList( value.ReferenceValueTexts.Select(text =>
new TranslationData
{
Text = text.Text, // string
Translations = text.TranslationDataValues.Select(translation => // List<TranslationValue>
new TranslationValue {
Culture = translation.Language.StrCulture,
TranslationText = translation.Value
}).ToList()
})); // don't ToList() here anymore
}
And here is another solution:
The GroupBy method takes an IEqualityComparer that can do the responsibility of comparing items for the grouping. But the problem is you used an anonymous type for the key in your grouping "GroupBy(x=>new{x.UnitPoints, x.Texts})". First we have to create a class to play the key role:
public class Key
{
public Key(decimal unitPoints, List<TranslationData> texts)
{
UnitPoints = unitPoints;
Texts = texts;
}
public decimal UnitPoints { get; set; }
public List<TranslationData> Texts { get; set; }
}
then we can implement the comparer:
public class Comparer : IEqualityComparer<Key>
{
public bool Equals(Key x, Key y)
{
if (x.UnitPoints != y.UnitPoints) return false;
if (!ListsAreEqual(x.Texts, y.Texts)) return false;
return true;
}
private bool ListsAreEqual(List<TranslationData> x, List<TranslationData> y)
{
if (x.Count != y.Count) return false;
for (int i = 0; i < x.Count; i++)
if (x[i].Text != y[i].Text)
return false;
return true;
}
public int GetHashCode(Key key)
{
int hash = 13;
hash = (hash * 7) + key.UnitPoints.GetHashCode();
foreach (var data in key.Texts)
hash = (hash * 7) + data.Text.GetHashCode();
return hash;
}
}
and finally this is what your query will look like:
var values2 = values
.Distinct()
.GroupBy(x => new Key(x.UnitPoints, x.Texts), new Comparer())
.Select(x => new
{
x.Key.UnitPoints,
Texts = x.Key.Texts,
Site = x.Select(y => y.SiteName)
}).ToList();
I think the first solution (creating the customized list class) is better, because you can also refactor your code and extract some logic to it.
Hi I'm a new to elastic nest API and I'm using nest 5.x. I'm currently developing some kind of advanced search page so when user doesn't check a criteria i don't have to include that filter on my query. I'm trying to combine 2 queries under must operator with object initializer approach using nest. How to achieve it? I'm following the example on [https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/bool-queries.html]
var secondSearchResponse = client.Search(new
SearchRequest {
Query = new TermQuery { Field = Field(p => p.Name), Value = "x" } &&
new TermQuery { Field = Field(p => p.Name), Value = "y" } });
But it doesnt work cause Field class doesnt accept type arguments.
I also tried to followed this approach from this topic
[Nest Elastic - Building Dynamic Nested Query
here is my code
public HttpResponseMessage GetSearchResult([FromUri] SearchModels queries)
{
try
{
///
string result = string.Empty;
result += "queryfields + " + queries.queryfields == null ? string.Empty : queries.queryfields;
result += "datefrom + " + queries.datefrom == null ? string.Empty : queries.datefrom;
result += "dateto + " + queries.dateto == null ? string.Empty : queries.dateto;
result += "emitentype + " + queries.emitentype == null ? string.Empty : queries.emitentype;
QueryContainer andQuery = null;
//List<QueryContainer> QueryContainers = new List<QueryContainer>();
IDXNetAnnouncement record = new IDXNetAnnouncement
{
kode_emiten = queries.kodeemiten
};
#region keyword
if (!string.IsNullOrEmpty(queries.queryfields))
{
var val = queries.queryfields;
TermQuery tq = new TermQuery
{
Field = queries.queryfields,
Value = val
};
if (andQuery == null)
andQuery = tq;
else
andQuery &= tq;
//QueryContainers.Add(tq);
}
#endregion keyword
#region kodeemiten
if (!string.IsNullOrEmpty(queries.kodeemiten))
{
var val = queries.kodeemiten;
TermQuery tq = new TermQuery
{
Name = "kode_emiten",
Field = record.kode_emiten,
Value = val
};
if (andQuery == null)
andQuery = tq;
else
andQuery &= tq;
//QueryContainers.Add(tq);
}
#endregion
#region date
if (!string.IsNullOrEmpty(queries.datefrom) && !string.IsNullOrEmpty(queries.dateto))
{
DateRangeQuery dq = new DateRangeQuery();
dq.Name = "tglpengumuman";
dq.LessThanOrEqualTo = DateMath.Anchored(queries.dateto);
dq.GreaterThanOrEqualTo = DateMath.Anchored(queries.datefrom);
dq.Format = "dd/mm/yyyy";
if (andQuery == null)
andQuery = dq;
else
andQuery &= dq;
//QueryContainers.Add(dq);
}
#endregion keyword
var reqs = (ISearchResponse<IDXNetAnnouncement>)null;
if (andQuery != null)
{
reqs = conn.client.Search<IDXNetAnnouncement>(s => s
.AllIndices()
.AllTypes()
.From(queries.indexfrom)
.Size(queries.pagesize)
.Query(q => q.Bool(qb => qb.Must(m => m.MatchAll() && andQuery))));
//var json = conn.client.Serializer.SerializeToString(reqs.ApiCall.ResponseBodyInBytes);
}
else
{
reqs = conn.client.Search<IDXNetAnnouncement>(s => s
.AllIndices()
.AllTypes()
.From(queries.indexfrom)
.Size(queries.pagesize)
.Query(m => m.MatchAll()));
}
//var reqstring = Encoding.UTF8.GetString(conn.client.);
var reslts = this.conn.client.Serializer.SerializeToString(reqs,SerializationFormatting.Indented);
var resp = new HttpResponseMessage()
{
Content = new StringContent(reslts)
};
resp.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return resp;
}
catch (Exception e)
{
var resp = new HttpResponseMessage()
{
Content = new StringContent(e.ToString())
};
resp.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return resp;
}
}
But that returns zero result. How to achieve this? Thx anyway.
EDIT :
This is the params variabel definition. Its apoco model of search keywords
public class SearchModels
{
public string queryfields { get; set; }
public string datefrom { get; set; }
public string dateto { get; set; }
public string emitentype { get; set; }
public string kodeemiten { get; set; }
public string issuercode { get; set; }
public int indexfrom { get; set; }
public int pagesize { get; set; }
}
IDXNetAnnouncement is a poco model of search result. Its actualy a document type which is stored on the elastic server
public class IDXNetAnnouncement
{
public string perihalpengumuman { get; set; }
public string attachments { get; set; }
public string createddate { get; set; }
public bool efekemiten_spei { get; set; }
public string jmsxgroupid { get; set; }
public string tglpengumuman { get; set; }
public object errordescription { get; set; }
public string ESversion { get; set; }
public int oldfinalid { get; set; }
public bool efekemiten_etf { get; set; }
public object errorcode { get; set; }
public string jenisemiten { get; set; }
public int pkid { get; set; }
public string judulpengumuman { get; set; }
public string form_id { get; set; }
public bool efekemiten_eba { get; set; }
public string jenispengumuman { get; set; }
public string nopengumuman { get; set; }
public string kode_emiten { get; set; }
public string divisi { get; set; }
public string EStimestamp { get; set; }
public bool efekemiten_obligasi { get; set; }
public long finalid { get; set; }
public bool efekemiten_saham { get; set; }
public string kodedivisi { get; set; }
public string SearchTerms
{
get
{
return string.Format("{0} {1} {2}", judulpengumuman, kode_emiten, nopengumuman);
}
}
}
But it doesnt work cause Field class doesnt accept type arguments.
You need to ensure that you include a using static directive for Nest.Infer i.e.
using static Nest.Infer;
with the rest of the using directives.
.Query(q => q.Bool(qb => qb.Must(m => m.MatchAll() && andQuery))));
No need to wrap in a Must(), just do
.Query(q => q.MatchAll() && andQuery)
which will wrap both queries in a bool query must clause. You also don't need to null check andQuery because NEST is smart enough to not combine the two queries if either or both are null.
if (!string.IsNullOrEmpty(queries.queryfields))
{
var val = queries.queryfields;
TermQuery tq = new TermQuery
{
Field = queries.queryfields,
Value = val
};
if (andQuery == null)
andQuery = tq;
else
andQuery &= tq;
//QueryContainers.Add(tq);
}
NEST has the concept of conditionless queries so you don't need to check it queries.queryfields is null or empty, simply build the query and add it to andQuery. So it would become
var val = queries.queryfields;
andQuery &= new TermQuery
{
Field = queries.queryfields,
Value = val
};
Aside
All of the NEST documentation is generated from source code; you can trace back to the original source file by clicking on any edit link within the documentation. This will take you to a github page, such as this one for bool queries. From here, the document contains an important note that links back to the original source.
How can I use a Lambda Expression on a List of Payment objects that have a List PaymentFields object. Here's what I currently have.
var list = paymentList.Where(payment => payment.PaymentFields.Any(field => field.FieldName == "ItemA" && field.FieldValue == "50");
That gives me payments that have ItemA as the Field Name and 50 as the Field Value. However, I want to COMPARE the two PaymentFields like so...
Where FieldName == "ItemA" && FieldValue = "50" && FieldName ItemA < FieldName ItemB
How would I do this?
I have two Objects:
public class Payment
{
public int Id { set; get; }
public string Name { set; get; }
public List<PaymentFields> PaymentFields { set; get; }
}
public class PaymentFields
{
public string FieldName { set; get; }
public string FieldValue { set; get; }
}
Here is an example Object:
var payment = new Payment()
{
Id = 1,
Name = "Test",
PaymentFields = new List<PaymentFields>()
{
new PaymentFields()
{
FieldName = "ItemA",
FieldValue = "20"
},
new PaymentFields()
{
FieldName = "ItemB",
FieldValue = "50"
}
}
};
Thanks for your help!
If you are absolutely sure there will be an "ItemA" and "ItemB", then this will work.
If either "ItemA" or "ItemB" is missing, it will throw an exception.
var list = paymentList
.Where(payment => payment.PaymentFields.Any(field => field.FieldName == "ItemA" && field.FieldValue == "50")
.Where(payment => payment.PaymentFields.First(field => field.FieldName == "ItemA").FieldValue
<
payment.PaymentFields.First(field => field.FieldName == "ItemB").FieldValue)
);
I have a class, which is created and populated from an xml string, I've simplified it for example purposes:
[XmlRoot("Person")]
public sealed class Person
{
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Location")]
public string Location { get; set; }
[XmlElement("Emails", Type = typeof(PersonEmails)]
public PersonEmails Emails { get; set; }
}
public class PersonEmails
{
[XmlElement("Email", Type = typeof(PersonEmail))]
public PersonEmail[] Emails { get; set; }
}
public class PersonEmail
{
[XmlAttribute("Type")]
public string Type { get; set; }
[XmlText]
public string Value { get; set; }
}
To extract the information, I'm trying to load them into another class, which is simply:
public class TransferObject
{
public string Name { get; set; }
public ObjectField[] Fields { get; set; }
}
public class ObjectField
{
public string Name { get; set; }
public string Value { get; set; }
}
I'm only populating "Fields" from the other object, which would simply be (Name = "Location", Value = "London"), but for Emails, (Name = "Email"+Type, Value = jeff#here.com)
Currently I can populate all the other fields, but I'm stuck with Emails, and knowing how to dig deep enough to be able to use reflection (or not) to get the information I need. Currently I'm using:
Person person = Person.FromXmlString(xmlString);
List<ObjectField> fields = new List<ObjectField>();
foreach (PropertyInfo pinfo in person.getType().GetProperties()
{
fields.Add(new ObjectField { Name = pinfo.Name, Value = pinfo.getValue(person, null).ToString();
}
How can I expand on the above to add all my emails to the list?
You are trying to type cast a complex values type to string value so you lost the data. Instead use following code:
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Name = "Person One";
person.Location = "India";
person.Emails = new PersonEmails();
person.Phones = new PersonPhones();
person.Emails.Emails = new PersonEmail[] { new PersonEmail() { Type = "Official", Value = "xyz#official.com" }, new PersonEmail() { Type = "Personal", Value = "xyz#personal.com" } };
person.Phones.Phones = new PersonPhone[] { new PersonPhone() { Type = "Official", Value = "789-456-1230" }, new PersonPhone() { Type = "Personal", Value = "123-456-7890" } };
List<ObjectField> fields = new List<ObjectField>();
fields = GetPropertyValues(person);
}
static List<ObjectField> GetPropertyValues(object obj)
{
List<ObjectField> propList = new List<ObjectField>();
foreach (PropertyInfo pinfo in obj.GetType().GetProperties())
{
var value = pinfo.GetValue(obj, null);
if (pinfo.PropertyType.IsArray)
{
var arr = value as object[];
for (var i = 0; i < arr.Length; i++)
{
if (arr[i].GetType().IsPrimitive)
{
propList.Add(new ObjectField() { Name = pinfo.Name + i.ToString(), Value = arr[i].ToString() });
}
else
{
var lst = GetPropertyValues(arr[i]);
if (lst != null && lst.Count > 0)
propList.AddRange(lst);
}
}
}
else
{
if (pinfo.PropertyType.IsPrimitive || value.GetType() == typeof(string))
{
propList.Add(new ObjectField() { Name = pinfo.Name, Value = value.ToString() });
}
else
{
var lst = GetPropertyValues(value);
if (lst != null && lst.Count > 0)
propList.AddRange(lst);
}
}
}
return propList;
}
}
Check this snippet out:
if(pinfo.PropertyType.IsArray)
{
// Grab the actual instance of the array.
// We'll have to use it in a few spots.
var array = pinfo.GetValue(personObject);
// Get the length of the array and build an indexArray.
int length = (int)pinfo.PropertyType.GetProperty("Length").GetValue(array);
// Get the "GetValue" method so we can extact the array values
var getValue = findGetValue(pinfo.PropertyType);
// Cycle through each index and use our "getValue" to fetch the value from the array.
for(int i=0; i<length; i++)
fields.Add(new ObjectField { Name = pinfo.Name, Value = getValue.Invoke(array, new object[]{i}).ToString();
}
// Looks for the "GetValue(int index)" MethodInfo.
private static System.Reflection.MethodInfo findGetValue(Type t)
{
return (from mi in t.GetMethods()
where mi.Name == "GetValue"
let parms = mi.GetParameters()
where parms.Length == 1
from p in parms
where p.ParameterType == typeof(int)
select mi).First();
}
You can definately do it with Reflection... You can take advantage of the fact that a Type can tell you if it's an array or not (IsArray)... and then take advantage of the fact that an Array has a method GetValue(int index) that will give you a value back.
Per your comment
Because Emails is a property within a different class, recursion should be used. However the trick is knowing when to go to the next level. Really that is up to you, but
if it were me, I would use some sort of Attribute:
static void fetchProperties(Object instance, List<ObjectField> fields)
{
foreach(var pinfo in instance.GetType().GetProperties())
{
if(pinfo.PropertyType.IsArray)
{
... // Code described above
}
else if(pinfo.PropertyType.GetCustomAttributes(typeof(SomeAttribute), false).Any())
// Go the next level
fetchProperties(pinfo.GetValue(instance), fields);
else
{
... // Do normal code
}
}
}
I have a search method that is trying to match a director name to an entry in one and/or two arrays. However, I cannot figure out how to print the names of multiple titles if the director has more than one movie in either array.
Right now my code looks like this:
if (director == myDVDs[i].Director || director == myBlu[i].Director)
{
Console.WriteLine("{0}: {1}", i + 1, myBlu[i].Title);
EndOptions();
}
else if (director != myDVDs[i].Director && director != myBlu[i].Director)
{
Console.WriteLine("{0} does not have a movie in the database try again", director);
Console.Clear();
Search();
}
You can use LINQ's Concat, like this:
var matching = myDVDs.Concat(myBlu).Where(d => d.Director == director);
int count = 1;
foreach (var m in matching) {
Console.WriteLine("{0}: {1}", count++, m.Title);
}
Generic solution which simplyfies adding a new movie type. To find titles used LINQ Select(), Where(), SelectMany(), yo might need adding using System.Linq to use these methods:
var dvds = new List<IMovie>
{
new DvdMovie {Title = "DVD1", Director = "Director1"},
new DvdMovie {Title = "DVD2", Director = "Director1"},
new DvdMovie {Title = "DVD3", Director = "Director2"}
};
var bluerays = new List<IMovie>
{
new BlueRayMovie {Title = "BR1", Director = "Director3"},
new BlueRayMovie {Title = "BR2", Director = "Director3"},
new BlueRayMovie {Title = "BR3", Director = "Director1"}
};
var allMovies = new List<IEnumerable<IMovie>> {dvds, bluerays};
string searchFor = "Director1";
// Main query, all other initialization code and types are below
IEnumerable<string> titles = allMovies.SelectMany(l =>
l.Where(sl => sl.Director == searchFor)
.Select(m => m.Title));
if (titles != null && titles.Count() > 0)
{
// OUTPUTs: DVD1, DVD2, BR3
foreach (var title in titles)
{
Console.WriteLine(title);
}
}
else
{
Console.WriteLine("Nothing found for " + searchFor);
}
Types
public interface IMovie
{
string Director { get; set; }
string Title { get; set; }
}
// TODO: Make immutable.
// Make setter private and introduce parametrized constructor
public class DvdMovie : IMovie
{
public string Director { get; set; }
public string Title { get; set; }
}
// TODO: Make immutable.
// Make setter private and introduce parametrized constructor
public class BlueRayMovie : IMovie
{
public string Director { get; set; }
public string Title { get; set; }
}