How to set an image rating? - c#

In my project, i have to set an image rating value in any format (*.png, *.jpg, *.bmp etc.), and return the value.
I try to use PropertyItem. it doesnt work.
Image im = Image.FromFile("D:\\2.jpg");
int intValue = 3;
byte[] intBytes = BitConverter.GetBytes(intValue);
if (BitConverter.IsLittleEndian)Array.Reverse(intBytes);
byte[] result = intBytes;
PropertyItem prop = im.GetPropertyItem(18246);
prop.Value = result;
im.SetPropertyItem(prop);
Does any one do this, if yes how, thanks?

To set up Rating You have to set up two values. Rating and RatingPercent
Exif.Image.Rating (18246)
Exif.Image.RatingPercent (18249)
These two values are corresponding to each other. So setting just Rating doesn't reflect Rating stars in Windows. (In fact it is very tricky because You set value, You can read it, but in Windows Explorer nothing changes).
class Program
{
static void Main(string[] args)
{
//0,1,2,3,4,5
SetRating(0);
SetRating(1);
SetRating(2);
SetRating(3);
SetRating(4);
SetRating(5);
}
private static void SetRating(short ratingValue)
{
short ratingPercentageValue = 0;
switch (ratingValue)
{
case 0: ratingPercentageValue = ratingValue; break;
case 1: ratingPercentageValue = ratingValue; break;
default: ratingPercentageValue = (short)((ratingValue - 1) * 25); break;
}
string SelectedImage = #"d:\Trash\phototemp\IMG_1200.JPG";
using (var imageTemp = System.Drawing.Image.FromFile(SelectedImage))
{
var rating = imageTemp.PropertyItems.FirstOrDefault(x => x.Id == 18246);
var ratingPercentage = imageTemp.PropertyItems.FirstOrDefault(x => x.Id == 18249);
rating.Value = BitConverter.GetBytes(ratingValue);
rating.Len= rating.Value.Length;
ratingPercentage.Value = BitConverter.GetBytes(ratingPercentageValue);
ratingPercentage.Len = ratingPercentage.Value.Length;
imageTemp.SetPropertyItem(rating);
imageTemp.SetPropertyItem(ratingPercentage);
imageTemp.Save(SelectedImage + "new" + ratingValue +".jpg");
}
}
}

The solution is not optimal (and hackish), but the easiest way is:
1) Use an image on your harddrive. Any image. Just rate it manually, so that the image has this property.
2) This image will work as your "dummy image", so that you can load it in order to call getPropertyItem() on it, to get the property.
3) Once you have the PropertyItem, just change its value, and use SetPropertyItem on your actual image.
string dummyFileName = #"C:\Users\<youruser>\Pictures\dummy.jpg";
string realFileName = #"C:\Users\<youruser>\Pictures\real.jpg";
string realFileNameOutput = #"C:\Users\<youruser\Pictures\real_rated.jpg";
Image dummyFile = Image.FromFile(dummyFileName);
var propertyItem = dummyFile.GetPropertyItem(18246);
Image realImage = Image.FromFile(realFileName);
realImage.SetPropertyItem(propertyItem);
realImage.Save(realFileNameOutput);

Related

ML.Net Rounding Float32 Results to 0 or 1

using System.Drawing;
using Microsoft.ML;
using Microsoft.ML.Data;
using Microsoft.ML.Transforms.Image;
namespace OnnxTest;
public static class Program
{
public static void Main(string[] args)
{
var tags = File.ReadLines(#"C:\Users\da3ds\Downloads\deepdanbooru-v3-20211112-sgd-e28\tags.txt");
var imageLocation = #"C:\Users\da3ds\Pictures\image.jpg";
var modelLocation = #"C:\Users\da3ds\Downloads\deepdanbooru-v3-20211112-sgd-e28\model-resnet-custom_v3.onnx";
MLContext mlContext = new MLContext();
Console.WriteLine("Read model");
Console.WriteLine($"Model location: {modelLocation}");
Console.WriteLine(
$"Default parameters: image size=({InputModel.imageWidth},{InputModel.imageHeight})");
Console.WriteLine($"Images location: {imageLocation}");
Console.WriteLine("");
Console.WriteLine("=====Identify the objects in the images=====");
Console.WriteLine("");
// Create IDataView from empty list to obtain input data schema
var data = new InputModel { ImagePath = imageLocation };
// Define scoring pipeline
var predictionEngine = GetPredictionEngine(mlContext, modelLocation);
var outputs = predictionEngine.Predict(data);
var outputMapped = tags.Zip(outputs.Scores).Select(t => new { Tag = t.First, f = t.Second })
.ToDictionary(a => a.Tag, a => a.f);
var outputTags = outputMapped.Where(a => Math.Abs(a.Value - 1) < 0.00001f).Select(a => a.Key).OrderBy(a => a)
.ToList();
}
private static PredictionEngine<InputModel, OutputModel> GetPredictionEngine(MLContext mlContext, string modelLocation)
{
var estimator = mlContext.Transforms.LoadImages(InputModel.ModelInput, "", nameof(InputModel.ImagePath))
.Append(mlContext.Transforms.ResizeImages(InputModel.ModelInput, InputModel.imageWidth,
InputModel.imageHeight, InputModel.ModelInput, ImageResizingEstimator.ResizingKind.IsoPad))
.Append(mlContext.Transforms.ExtractPixels(InputModel.ModelInput, InputModel.ModelInput))
.Append(mlContext.Transforms.ApplyOnnxModel(OutputModel.ModelOutput, InputModel.ModelInput,
modelLocation));
var transformer = estimator.Fit(mlContext.Data.LoadFromEnumerable(Array.Empty<InputModel>()));
// Fit scoring pipeline
var predictionEngine = mlContext.Model.CreatePredictionEngine<InputModel, OutputModel>(transformer);
return predictionEngine;
}
class InputModel
{
public const int imageHeight = 512;
public const int imageWidth = 512;
// input tensor name
public const string ModelInput = "input_1:0";
public string ImagePath { get; set; }
[ColumnName(ModelInput)]
[ImageType(imageHeight, imageWidth)]
public Bitmap Image { get; set; }
}
class OutputModel
{
// output tensor name
public const string ModelOutput = "Identity:0";
[ColumnName(ModelOutput)]
public float[] Scores { get; set; }
}
}
I wrote up a very simple test program to try to get an output that matches a python project, only in C# so I could efficiently use it in an ASP.Net api (also just prefer C#). The original Python works, even after I modified it to use onnxruntime instead of keras, which is where the model originated. It gives a float[9176] of scores 0-1, which matches a list of tags in tags.txt, for whether that tag should apply to a given image.
It's a multi-classification problem with TensorFlow. I used the object detection sample to get here, and it returns a result, and the result is...correct, but not. It's rounding for whatever reason.
I'm new to ML and ML.Net has very little out there, so I figured I'd use my first question in a long time and hoping someone can shed some light on this for me.
Ok, new day. I traced the code path of the python project and made an MVP. In doing so, I have very few things to look at the difference of.
import os
import onnxruntime
import skimage.transform
import tensorflow as tf
def main():
# disable CUDA acceleration for simplicity in running the test
# you need drivers, an nvidia gpu, etc. for that
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
image_path = 'C:\\Users\\da3ds\\Pictures\\image.jpg'
model_path = 'C:\\Users\\da3ds\\Downloads\\deepdanbooru-v3-20211112-sgd-e28\\model-resnet-custom_v3.onnx'
# load tags
tags_path = 'C:\\Users\\da3ds\\Downloads\\deepdanbooru-v3-20211112-sgd-e28\\tags.txt'
with open(tags_path, 'r') as tags_stream:
tags = [tag for tag in (tag.strip() for tag in tags_stream) if tag]
# create inference session
model = onnxruntime.InferenceSession(model_path, providers=['CPUExecutionProvider'])
width = model.get_inputs()[0].shape[1] # 512
height = model.get_inputs()[0].shape[2] # 512
image_raw = tf.io.read_file(image_path)
image = tf.io.decode_png(image_raw, channels=3)
image = tf.image.resize(image, size=(width, height), method=tf.image.ResizeMethod.AREA, preserve_aspect_ratio=True)
image = image.numpy() # EagerTensor to np.array
image_width = image.shape[0]
image_height = image.shape[1]
t = skimage.transform.AffineTransform(translation=(-image_width * 0.5, -image_height * 0.5))
t += skimage.transform.AffineTransform(translation=(width * 0.5, height * 0.5))
image = skimage.transform.warp(image, t.inverse, output_shape=(width, height), order=1, mode='edge')
# at this point all widths and heights are probably 512
# normalize the image
image = image / 255.0
image_shape = image.shape
# build the input shape of Vector<1, 512, 512, 3>
image = image.reshape((1, image_shape[0], image_shape[1], image_shape[2]))
onnx_result = model.run(None, {'input_1:0': image})
# onnx_result is 2 arrays deep for reason
# 1 would make sense, as it can handle batches
onnx_result = onnx_result[0][0]
# print a nice result
for i, tag in enumerate(tags):
print(f'({onnx_result[i]:05.3f}) {tag}')
if __name__ == '__main__':
main()
Conveniently, in doing so, I made a mistake in a default value that yielded the same result as the ML.Net results: (not) Normalizing the Image. I couldn't figure out how to do that in the ML.Net pipeline, so I made the array with Magick.Net and fed it to ML.Net directly.
Here's the final code:
using ImageMagick;
using Microsoft.ML;
using Microsoft.ML.Data;
namespace OnnxTest;
public static class Program
{
public static void Main(string[] args)
{
var tags = File.ReadLines(#"C:\Users\da3ds\Downloads\deepdanbooru-v3-20211112-sgd-e28\tags.txt");
var imageLocation = #"C:\Users\da3ds\Pictures\image.jpg";
var modelLocation = #"C:\Users\da3ds\Downloads\deepdanbooru-v3-20211112-sgd-e28\model-resnet-custom_v3.onnx";
MLContext mlContext = new MLContext(seed: 0);
Console.WriteLine("Read model");
Console.WriteLine($"Model location: {modelLocation}");
Console.WriteLine(
$"Default parameters: image size=({InputModel.Width},{InputModel.Height})");
Console.WriteLine($"Images location: {imageLocation}");
Console.WriteLine("");
Console.WriteLine("=====Identify the objects in the images=====");
Console.WriteLine("");
// Create IDataView from empty list to obtain input data schema
var data = new InputModel { Data = GetImage(imageLocation) };
// Define scoring pipeline
var predictionEngine = GetPredictionEngine(mlContext, modelLocation);
var output = predictionEngine.Predict(data);
var outputMapped = tags.Zip(output.Scores).Select(t => new { Tag = t.First, f = t.Second })
.ToDictionary(a => a.Tag, a => a.f);
var outputTags = outputMapped.Where(a => a.Value > 0.80f).Select(a => (Tag: a.Key, Score: a.Value))
.ToList();
foreach (var tag in outputTags)
{
Console.WriteLine($"({tag.Score:P1}) {tag.Tag}");
}
}
private static PredictionEngine<InputModel, OutputModel> GetPredictionEngine(MLContext mlContext, string modelLocation)
{
var transformer = GetBasicTransformer(mlContext, modelLocation);
// Fit scoring pipeline
var predictionEngine = mlContext.Model.CreatePredictionEngine<InputModel, OutputModel>(transformer);
return predictionEngine;
}
private static ITransformer GetBasicTransformer(MLContext mlContext, string modelLocation)
{
var estimator = mlContext.Transforms.ApplyOnnxModel(OutputModel.ModelOutput, InputModel.ModelInput,
modelLocation);
var transformer = estimator.Fit(mlContext.Data.LoadFromEnumerable(Array.Empty<InputModel>()));
return transformer;
}
public static float[] GetImage(string imagePath)
{
using var mImage = new MagickImage(imagePath);
mImage.Quality = 100;
mImage.BackgroundColor = new MagickColor(0, 0, 0);
mImage.HasAlpha = false;
mImage.Resize(new MagickGeometry($"{InputModel.Width}>x{InputModel.Height}>"));
mImage.Extent(InputModel.Width, InputModel.Height, Gravity.Center, new MagickColor(0,0,0));
var pixels = mImage.GetPixels();
var array = pixels.ToArray();
var data = new float[InputModel.Width * InputModel.Height * InputModel.Channels];
for (var index = 0; index < array.Length; index++)
{
data[index] = array[index] / 255.0f;
}
return data;
}
class InputModel
{
public const int Width = 512;
public const int Height = 512;
public const int Channels = 3;
public const string ModelInput = "input_1:0";
[ColumnName(ModelInput)]
[VectorType(1, Width, Height, Channels)]
public float[] Data { get; set; }
}
class OutputModel
{
// output tensor name
public const string ModelOutput = "Identity:0";
[ColumnName(ModelOutput)]
public float[] Scores { get; set; }
}
}
Obviously, the final...final code will be less of an MVP, but this was a test. I leave this as a trail of my efforts in case someone else hits a similar issue. At the very least, it gives my debugging steps and some sample code. Thanks for being my rubber ducks.

The instance of entity type +cannot be tracked because another instance with the same key value

I have the following code
double csave = (double)FindAcc(memid).MovAmt;
double psave = (double)FindAcc(memid).VolSafe;
double psaveamt = (double)FindAcc(memid).PriSave;
Single compAmt = 0;
ToxAccInfo ta = new ToxAccInfo();
ta.MemId = (int?)memid;
ta.AccId = (long)FindAcc(memid).AccId;
The ta.AccID is the primary key for the table ToxAccInfo.
Issue is when I attempt to save changes without the ta.AccId = (long)Find(memid).Accid, it creates another line instead of updating the original. But, when I now add the line, it fails with the error The instance of entity type...cannot be tracked because another instance with the same key value.
I have spent the past 3 days trying to find a work around but still can't get it to work.
Thank you.
I am adding the whole procedure to create what is to be saved (ta) and the saving subroutine
public IEnumerable<ToxAccInfo> UpdateBoth(DateTime stdate, DateTime spdate, long memid, bool chkcomp, bool chkvol)
{
double csave = (double)FindAcc(memid).MovAmt;
double psave = (double)FindAcc(memid).VolSafe;
double psaveamt = (double)FindAcc(memid).PriSave;
Single compAmt = 0;
var ta = new ToxAccInfo();
ta.MemId = (int?)memid;
//ta.AccId = (long)FindAcc(memid).AccId;
for (DateTime curdate = stdate; curdate <= spdate; curdate = curdate.AddMonths(1))
{
if (GoAhead(memid, curdate))
{
ta.MovAmt = csave;
ta.VolSafe = psave;
if (chkvol == true)
{
ta.VolSafe = psave + psaveamt;
//ta.VolSafe += psaveamt;
}
if (curdate.Month > 1 && curdate.Year >= 2011)
{
compAmt = 1000;
}
else
{
compAmt = 200;
}
if (chkcomp == true)
{
ta.MovAmt = csave + compAmt; //do the check for year it changed to 1000
}
ta.Done = curdate;
ta.Udate = curdate;
ta.Amt = compAmt + psaveamt;
//check for interest for month 12 and year greater than 2007.
if (curdate.Month == 12 && curdate.Year >= 2017)
{
ta.VolSafe = doInterest((decimal)ta.VolSafe);
ta.MovAmt = doInterest((decimal)ta.MovAmt);
psave = (double)ta.VolSafe;
csave = (double)ta.MovAmt;
goto jumper;
}
//psave += psaveamt;
//csave += compAmt;
psave = (double)ta.VolSafe;
csave = (double)ta.MovAmt;
jumper:;
}
}
yield return UpdateMemAccount(ta);
}
private ToxAccInfo UpdateMemAccount(ToxAccInfo UpAm)
{
_db.ToxAccInfos.Update(UpAm);
_db.SaveChanges();
return null;
}
The error occurs after the yield calls private ToxAccInfo UpdateMemAccount(ToxAccInfo UpAm), at _db.ToxAccinfos.Update(UpAm);, I have tried using add instead of Update still the same issue.
I call Updateboth from the controller with the following code, the two methods are in an interface. The code below calls the interface.
case "Commit Update":
ViewData["taa"] = _ar.UpdateBoth(stdate, eddate, (int)_ar.FindAcc(long.Parse(HttpContext.Session.GetString("memberid"))).MemId, chkcomp, chkvol).ToList();
ViewBag.isSet = false;
break;
Here is what i assuming your code doing:
FindAcc(memid) will use the DbContext to select the entity base on the memid (this is your unique value), then extract the value out of it.
After that, you create another instance and assign the same key to that along with the modified values you want.
But unfortunately, the AccId is your PK, and ta.AccId = (long)Find(memid).Accid make it another new instance with the same Id that got tracked by the EF Core itself while it select the entity back by the FindAcc(memid).
What you might want to do:
If you want to update that entity:
var entityFromDb = FindAcc(memid);
entityFromDb.volSafe = SomeNewValueHere;
YourDbContext.SaveChange();
If you want to add another entity:
var entityFromDb = FindAcc(memid);
var ta = new ToxAccInfo();
ta.volSafe = SomeNewValueHere;
YourDbContext.YourToxAccInfoSet.Add(ta);
YourDbContext.SaveChange();
Furthur information can be found here

Trouble finding tab character in list objects

I am working in C#, winforms application.
I am reading from a text file where each row has fields divided by tabs:
I am putting each row in a list named tic_string. From here I am trying to search each list object, find the tabs, and put each field in its own array. So there will be an array for column a, column b, column c ... etc.
The problem is when I try to find the tabs in my list objects, it finds nothing. Here is my code:
string[] tic_num = new string[row_counter];
string[] tic_title = new string[row_counter];
string[] tic_owner = new string[row_counter];
string[] tic_open_date = new string[row_counter];
int last_tab = 0;
int char_counter = 0;
int feild_counter = 1;
int feild_char_count = 1;
int current_row=0;
string temp_feild = "";
char temp_char;
char tab_char = '\t';
foreach (string tic_string_value in tic_string)
{
temp_char = tic_string_value[char_counter];
if (temp_char == tab_char)
{
Console.WriteLine("tab_found");
if (feild_char_count == 1)
{
temp_feild = "";
}
else
{
temp_feild = tic_string_value.Substring(last_tab, feild_char_count);
}
last_tab = char_counter;
feild_char_count = 0;
switch (feild_counter)
{
case 1:
tic_num[current_row] = temp_feild;
break;
case 2:
tic_title[current_row] = temp_feild;
break;
case 3:
tic_owner[current_row] = temp_feild;
break;
case 4:
tic_open_date[current_row] = temp_feild;
break;
}
}
current_row++;
feild_char_count++;
char_counter++;
if (feild_counter == 5)
feild_counter = 1;
}
Your code seems to be too complicated for such simple task. Do not parse each line char by char, just use helper functions like String.Split etc.:
foreach (string tic_string_value in tic_string)
{
var parts = tic_string_value.Split(new [] { '\t' },
StringSplitOptions.RemoveEmptyEntries);
tic_num[current_row] = parts[0];
tic_title[current_row] = parts[1];
tic_owner[current_row] = parts[2];
tic_open_date[current_row] = parts[3];
current_row++;
}
First of all, I deduce from the style of your code that you are probably familiar with C/C++ and are new to C#, because this code has a particularly "C++" flavour to it. It reminds me very much of my own C# code when I first made the jump myself.
I am glad that you described the problem you are trying to solve rather than simply posting the code and asking where to find the bug because I think you can actually solve your problem much more simply.
Considering the following code (this assumes that you're iterating over each of the rows outside this code, and I omit some of the declaring of variables that you had already specified):
int field_counter = 0;
foreach (var field in tic_string.Split('\t')) {
switch (field_counter++) {
case 0:
tic_num[current_row] = field;
break;
case 1:
tic_title[current_row] = field;
break;
case 2:
tic_owner[current_row] = field;
break;
case 3:
tic_open_date[current_row] = field;
break;
}
}
This leverages the succinctness of C# and removes quite a few lines of code, which is always good. The String.Split method will take care of most of the string splitting for you, so there's no need to do it all manually and keep track of characters.
Note: I kept your original naming of some of the field names, although generally it is preferable to use CamelCase in C# code.
Now I notice from your original code that it's possible you don't have "rows" in your data in an actual sense (i.e. split by newline characters) but rather you may have the data entirely tab separated and are using the fact that you have a fixed number of columns per row to split up rows.
If this was the case, might I suggest the following code block could help you:
int i = 0;
foreach (var group in tic_string.GroupBy(x => i++ % 4)) {
int current_row = 0;
foreach (var field in group) {
switch (group.Key) {
case 0:
tic_num[current_row] = field;
break;
case 1:
tic_title[current_row] = field;
break;
case 2:
tic_owner[current_row] = field;
break;
case 3:
tic_open_date[current_row] = field;
break;
}
current_row++;
}
}
Now of course you may need to adapt these blocks to your code rather than use it verbatim. I hope that they at least demonstrate a different way of thinking about the problem. In particular, learning to use the various LINQ extension methods and LINQ queries will also be very helpful - they are part of what allows C# code to be so quick and easy to develop.
Best of luck in solving your problem!
You could also use a list instead of 4 string arrays:
public class ObjectToBeUsed
{
public Person(int num, string title, string owner, string opendate)
{
this.Num = num;
this.Title = title;
this.Owner = owner;
this.OpenDate = opendate;
}
private int _num;
public int Num
{
get { return _num; }
set { _num = value; }
}
private string _title;
public string Title
{
get { return _title; }
set { _title = value; }
}
private string _owner;
public string Owner
{
get { return _owner; }
set { _owner = value; }
}
private string _opendate;
public string OpenDate
{
get { return _opendate; }
set { _opendate = value; }
}
}
This is the class which describes each row in your text file.
System.IO.StreamReader file = new System.IO.StreamReader("test.txt");
string currentLine = null;
List<ObjectToBeUsed> peopleList = new List<ObjectToBeUsed>();
while ((currentLine = file.ReadLine()) != null)
{
string[] tokens = Regex.Split(currentLine, #"\t");
peopleList.Add(new ObjectToBeUsed(Convert.ToInt32(tokens[0]), tokens[1], tokens[2], tokens[3]));
}
The code is pretty self-explanatory, but if you need any further explaining, go ahead.

ASP.NET Dynamic gaming forum signatures

How could I create a dynamic forum signature generator using ASP.NET MVC. I currently have a Stats fetcher that retrieves the user info to be used in the Forum Signature.
I'm trying to create a forum signature generator where a user can enter their user name and generate an image that they can put in their forum signature that will show everyone the users stats.
something like this http://www.xfire.com/miniprofile
I must have lost track of what I was doing I didn't mean supply such little info, But I think you will have an idea of what im trying to do now..
i would use abcPdf component, the image would be a hi-res pdf document.
you would then just need to pass text, font, color, x, y, w, h
then your render the PDF out as a jpg stream
a basic idea to get you going could be like this;
private void addTextToPDF(string cmyk, int fs, string fontname, Double posx,
Double posY, Double mWidth, Double mHeight, String text, Double hpos)
{
text = secure.reverseCleanup(text);
int lettercount1 = 0;
foreach (char c in text)
{ lettercount1 ++; }
TheDoc.Color.String = cmyk;
TheDoc.FontSize = fs;
var theFont = fontname;
TheDoc.Rect.Position(posx, posY);
TheDoc.Rect.Width = mWidth;
TheDoc.Rect.Height = mHeight;
TheDoc.HPos = hpos;
TheDoc.Font = TheDoc.EmbedFont(theFont, "Latin", false, true, true);
int didwrite = TheDoc.AddText(text);
string addedchars = TheDoc.GetInfo(didwrite, "Characters");
var oldid = didwrite;
if (addedchars != lettercount1.ToString())
didwrite = 0;
while (didwrite==0) // hits this if first run did not add text
{
TheDoc.Delete(oldid);
fs = fs - 2;
TheDoc.Color.String = cmyk;
TheDoc.FontSize = fs;
theFont = fontname;
TheDoc.Rect.Position(posx, posY);
TheDoc.Rect.Width = mWidth;
TheDoc.Rect.Height = mHeight;
TheDoc.HPos = hpos;
TheDoc.Font = TheDoc.EmbedFont(theFont, "Latin", false, true, true);
didwrite = TheDoc.AddText(secure.reverseCleanup(text));
addedchars = TheDoc.GetInfo(didwrite, "Characters");
oldid = didwrite;
if (addedchars != lettercount1.ToString())
didwrite = 0;
}
}
public byte[] convertPDFToImageStream()
{
byte[] jpgBytes = null;
byte[] theData = null;
theData = TheDoc.GetData();
TheDoc.Clear();
TheDoc.Read(theData);
TheDoc.Rendering.DotsPerInch = getDPI();
TheDoc.Rendering.ColorSpace = "RGB";
jpgBytes = TheDoc.Rendering.GetData("preview.jpg");
return jpgBytes;
}
that is the code to add text and also to render the PDF out as a stream JPG
very very good component.

How to set array length in c# dynamically

I am still new to C# and I've been struggling with various issues on arrays. I've got an array of metadata objects (name value pairs) and I would like to know how to create only the number of "InputProperty" objects that I truly need. In this loop I've arbitrarily set the number of elements to 20 and I try to bail out when the entry becomes null but the web service on the receiving end of this does not like any null elements passed to it:
private Update BuildMetaData(MetaData[] nvPairs)
{
Update update = new Update();
InputProperty[] ip = new InputProperty[20]; // how to make this "dynamic"
int i;
for (i = 0; i < nvPairs.Length; i++)
{
if (nvPairs[i] == null) break;
ip[i] = new InputProperty();
ip[i].Name = "udf:" + nvPairs[i].Name;
ip[i].Val = nvPairs[i].Value;
}
update.Items = ip;
return update;
}
In summary, say I only have 3 namevalue pairs in the above input array? Rather than allocate 20 elements for the array called ip, how can code this so ip is only as big as it needs to be. The update object is passed across another webservice so serialization is important (i.e. I can't use namevaluecollection, etc.).
p.s. Is the only way to followup on a posted question through the "add comments" facility?
InputProperty[] ip = new InputProperty[nvPairs.Length];
Or, you can use a list like so:
List<InputProperty> list = new List<InputProperty>();
InputProperty ip = new (..);
list.Add(ip);
update.items = list.ToArray();
Another thing I'd like to point out, in C# you can delcare your int variable use in a for loop right inside the loop:
for(int i = 0; i<nvPairs.Length;i++
{
.
.
}
And just because I'm in the mood, here's a cleaner way to do this method IMO:
private Update BuildMetaData(MetaData[] nvPairs)
{
Update update = new Update();
var ip = new List<InputProperty>();
foreach(var nvPair in nvPairs)
{
if (nvPair == null) break;
var inputProp = new InputProperty
{
Name = "udf:" + nvPair.Name,
Val = nvPair.Value
};
ip.Add(inputProp);
}
update.Items = ip.ToArray();
return update;
}
If you don't want to use a List, ArrayList, or other dynamically-sized collection and then convert to an array (that's the method I'd recommend, by the way), then you'll have to allocate the array to its maximum possible size, keep track of how many items you put in it, and then create a new array with just those items in it:
private Update BuildMetaData(MetaData[] nvPairs)
{
Update update = new Update();
InputProperty[] ip = new InputProperty[20]; // how to make this "dynamic"
int i;
for (i = 0; i < nvPairs.Length; i++)
{
if (nvPairs[i] == null) break;
ip[i] = new InputProperty();
ip[i].Name = "udf:" + nvPairs[i].Name;
ip[i].Val = nvPairs[i].Value;
}
if (i < nvPairs.Length)
{
// Create new, smaller, array to hold the items we processed.
update.Items = new InputProperty[i];
Array.Copy(ip, update.Items, i);
}
else
{
update.Items = ip;
}
return update;
}
An alternate method would be to always assign update.Items = ip; and then resize if necessary:
update.Items = ip;
if (i < nvPairs.Length)
{
Array.Resize(update.Items, i);
}
It's less code, but will likely end up doing the same amount of work (i.e. creating a new array and copying the old items).
Use this:
Array.Resize(ref myArr, myArr.Length + 5);
Does is need to be an array? If you use an ArrayList or one of the other objects available in C#, you won't have this limitation to content with. Hashtable, IDictionnary, IList, etc.. all allow a dynamic number of elements.
You could use List inside the method and transform it to an array at the end. But i think if we talk about an max-value of 20, your code is faster.
private Update BuildMetaData(MetaData[] nvPairs)
{
Update update = new Update();
List<InputProperty> ip = new List<InputProperty>();
for (int i = 0; i < nvPairs.Length; i++)
{
if (nvPairs[i] == null) break;
ip[i] = new InputProperty();
ip[i].Name = "udf:" + nvPairs[i].Name;
ip[i].Val = nvPairs[i].Value;
}
update.Items = ip.ToArray();
return update;
}
Or in C# 3.0 using System.Linq you can skip the intermediate list:
private Update BuildMetaData(MetaData[] nvPairs)
{
Update update = new Update();
var ip = from nv in nvPairs
select new InputProperty()
{
Name = "udf:" + nv.Name,
Val = nv.Value
};
update.Items = ip.ToArray();
return update;
}
Use Array.CreateInstance to create an array dynamically.
private Update BuildMetaData(MetaData[] nvPairs)
{
Update update = new Update();
InputProperty[] ip = Array.CreateInstance(typeof(InputProperty), nvPairs.Count()) as InputProperty[];
int i;
for (i = 0; i < nvPairs.Length; i++)
{
if (nvPairs[i] == null) break;
ip[i] = new InputProperty();
ip[i].Name = "udf:" + nvPairs[i].Name;
ip[i].Val = nvPairs[i].Value;
}
update.Items = ip;
return update;
}
Typically, arrays require constants to initialize their size. You could sweep over nvPairs once to get the length, then "dynamically" create an array using a variable for length like this.
InputProperty[] ip = (InputProperty[])Array.CreateInstance(typeof(InputProperty), length);
I wouldn't recommend it, though. Just stick with the
List<InputProperty> ip = ...
...
update.Items = ip.ToArray();
solution. It's not that much less performant, and way better looking.
You can create an array dynamically in this way:
static void Main()
{
// Create a string array 2 elements in length:
int arrayLength = 2;
Array dynamicArray = Array.CreateInstance(typeof(int), arrayLength);
dynamicArray.SetValue(234, 0); // → a[0] = 234;
dynamicArray.SetValue(444, 1); // → a[1] = 444;
int number = (int)dynamicArray.GetValue(0); // → number = a[0];
int[] cSharpArray = (int[])dynamicArray;
int s2 = cSharpArray[0];
}

Categories

Resources