Hello and welcome! Today, we're exploring practical data manipulation techniques in C#. We'll use C# lists to represent our data stream and perform projection, filtering, and aggregation. And here's the star of the show: our operations will be neatly packaged within a C# class! No mess, all clean code.
Data manipulation is akin to being a sculptor but for data. We chisel and shape our data to get the desired structure. C# lists
are perfect for this, and our operations will be conveniently bundled inside a C# class
. So, let's get our toolbox ready! Here's a simple C# class, DataStream
, that will serve as our toolbox:
C#1using System; 2using System.Collections.Generic; 3using System.Linq; 4 5public class DataStream<T> 6{ 7 public List<T> Data { get; set; } 8 9 public DataStream(List<T> data) 10 { 11 Data = data; 12 } 13}
Our first stop is data projection. Think of it like capturing a photo of our desired features. Suppose we have data about people. If we're only interested in names and ages, we project our data to include just these details. We'll extend our DataStream
class with a ProjectData
method for this:
C#1using System.Collections.Generic; 2using System.Linq; 3 4public class DataStream<T> 5{ 6 public List<T> Data { get; set; } 7 8 public DataStream(List<T> data) 9 { 10 Data = data; 11 } 12 13 public List<dynamic> ProjectData(List<string> keys) 14 { 15 var projectedData = Data.Select(d => 16 { 17 var newObj = new Dictionary<string, object>(); 18 foreach (var key in keys) 19 { 20 var propertyInfo = d.GetType().GetProperty(key); 21 newObj[key] = propertyInfo?.GetValue(d, null); 22 } 23 return (dynamic)newObj; 24 }).ToList(); 25 26 return projectedData; 27 } 28} 29 30public class Program 31{ 32 public static void Main() 33 { 34 var ds = new DataStream<dynamic>(new List<dynamic> 35 { 36 new { Name = "Alice", Age = 25, Profession = "Engineer" }, 37 new { Name = "Bob", Age = 30, Profession = "Doctor" }, 38 }); 39 40 var projectedDs = ds.ProjectData(new List<string> { "Name", "Age" }); 41 foreach (var item in projectedDs) 42 { 43 Console.WriteLine($"{item["Name"]}, {item["Age"]}"); 44 } 45 // Outputs: 46 // Alice, 25 47 // Bob, 30 48 } 49}
As you can see, we now have a new list with just the names and ages!
Next, we have data filtering, which is like cherry-picking our preferred data entries. We'll extend our DataStream
class with a FilterData
method that uses a "test" function to filter data:
C#1using System; 2using System.Collections.Generic; 3using System.Linq; 4 5public class DataStream<T> 6{ 7 public List<T> Data { get; set; } 8 9 public DataStream(List<T> data) 10 { 11 Data = data; 12 } 13 14 // Existing methods... 15 16 public List<T> FilterData(Func<T, bool> testFunc) 17 { 18 return Data.Where(testFunc).ToList(); 19 } 20} 21 22public class Program 23{ 24 public static void Main() 25 { 26 var ds = new DataStream<dynamic>(new List<dynamic> 27 { 28 new { Name = "Alice", Age = 25, Profession = "Engineer" }, 29 new { Name = "Bob", Age = 30, Profession = "Doctor" }, 30 }); 31 32 Func<dynamic, bool> ageTest = x => x.Age > 26; // our "test" function 33 var filteredDs = ds.FilterData(ageTest); 34 foreach (var item in filteredDs) 35 { 36 Console.WriteLine($"{item.Name}, {item.Age}, {item.Profession}"); 37 } 38 // Outputs: 39 // Bob, 30, Doctor 40 } 41}
With the filter method, our output is a list with only Bob’s data, as he's the only one who passes the 'age over 26' test.
Last is data aggregation, where we condense our data into a summary. We will add an AggregateData
method to our DataStream
class for this:
C#1using System; 2using System.Collections.Generic; 3using System.Linq; 4 5public class DataStream<T> 6{ 7 public List<T> Data { get; set; } 8 9 public DataStream(List<T> data) 10 { 11 Data = data; 12 } 13 14 // Existing methods... 15 16 public double AggregateData(string key, Func<List<dynamic>, double> aggFunc) 17 { 18 var values = Data.Select(d => 19 { 20 var propertyInfo = d.GetType().GetProperty(key); 21 return (dynamic)propertyInfo?.GetValue(d, null); 22 }).ToList(); 23 24 return aggFunc(values); 25 } 26} 27 28public class Program 29{ 30 public static void Main() 31 { 32 var ds = new DataStream<dynamic>(new List<dynamic> 33 { 34 new { Name = "Alice", Age = 25, Profession = "Engineer" }, 35 new { Name = "Bob", Age = 30, Profession = "Doctor" }, 36 }); 37 38 var averageAge = ds.AggregateData("Age", values => values.Average(v => (double)v)); 39 Console.WriteLine(averageAge); // Outputs: 27.5 40 } 41}
With this script, we get the average age of Alice and Bob, which is 27.5
.
Now, let's combine projection, filtering, and aggregation to see the collective power of these techniques. We'll extend our example to demonstrate this flow:
We'll modify our DataStream
class to include all the methods and then use them together in a workflow. On top of that, projection and filtering methods will now return an instance of DataStream<T>
, not a list as before, so that we can chain these methods when calling them:
C#1using System; 2using System.Collections.Generic; 3using System.Linq; 4 5public class DataStream<T> 6{ 7 public List<T> Data { get; set; } 8 9 public DataStream(List<T> data) 10 { 11 Data = data; 12 } 13 14 public DataStream<dynamic> ProjectData(List<string> keys) 15 { 16 var projectedData = Data.Select(d => 17 { 18 var newObj = new Dictionary<string, object>(); 19 foreach (var key in keys) 20 { 21 var propertyInfo = d.GetType().GetProperty(key); 22 newObj[key] = propertyInfo?.GetValue(d, null); 23 } 24 return (dynamic)newObj; 25 }).ToList(); 26 27 return new DataStream<dynamic>(projectedData); 28 } 29 30 public DataStream<T> FilterData(Func<T, bool> testFunc) 31 { 32 var filteredData = Data.Where(testFunc).ToList(); 33 return new DataStream<T>(filteredData); 34 } 35 36 public double AggregateData(string key, Func<List<dynamic>, double> aggFunc) 37 { 38 var values = Data.Select(d => 39 { 40 var propertyInfo = d.GetType().GetProperty(key); 41 return (dynamic)propertyInfo?.GetValue(d, null); 42 }).ToList(); 43 44 return aggFunc(values); 45 } 46} 47 48public class Program 49{ 50 public static void Main() 51 { 52 var ds = new DataStream<dynamic>(new List<dynamic> 53 { 54 new { Name = "Alice", Age = 25, Profession = "Engineer", Salary = 70000 }, 55 new { Name = "Bob", Age = 30, Profession = "Doctor", Salary = 120000 }, 56 new { Name = "Carol", Age = 35, Profession = "Artist", Salary = 50000 }, 57 new { Name = "David", Age = 40, Profession = "Engineer", Salary = 90000 }, 58 }); 59 60 // Step 1: Project the data to include only 'Name', 'Age', and 'Salary' 61 var projectedDs = ds.ProjectData(new List<string> { "Name", "Age", "Salary" }); 62 63 // Step 2: Filter the projected data to include only those with age > 30 64 var filteredDs = projectedDs.FilterData(x => x.Age > 30); 65 66 // Step 3: Aggregate the filtered data to compute the average salary 67 var averageSalary = filteredDs.AggregateData("Salary", salaries => salaries.Average(v => (double)v)); 68 Console.WriteLine(averageSalary); // Outputs: 70000.0 69 } 70}
Here:
Name
, Age
, and Salary
fields from our data. The ProjectData
method now returns a DataStream<dynamic>
object, allowing us to chain multiple operations.FilterData
method also returns a DataStream<T>
object for chaining.70,000
.By combining these methods, our data manipulation becomes both powerful and concise. Try experimenting and see what you can create!
Brilliant job! You've now grasped the basics of data projection, filtering, and aggregation on C# lists. Plus, you've learned to package these operations in a C# class
— a neat bundle of reusable code magic!
Now, why not try applying these fresh skills with some practice exercises? They're just around the corner. Ready? Let's dive into more fun with data manipulation!