Last active
November 30, 2020 11:37
-
-
Save rstropek/1430b24436f948f298b7d63a72896b4f to your computer and use it in GitHub Desktop.
C# Records
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var heroes = new Hero[] { | |
new("Homelander", "DC", true), | |
new("Jessica Jones", "Marvel", false), | |
}; | |
class Hero { | |
public Hero(string name, string universe, bool canFly) => | |
(Name, Universe, CanFly) = (name, universe, canFly); | |
public string Name { get; } | |
public string Universe { get; } | |
public bool CanFly { get; } | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var heroes = new Hero[] { | |
// Note auto-generated constructor | |
new("Homelander", "DC", true), | |
new("Jessica Jones", "Marvel"), | |
}; | |
// Note auto-generated deconstructor | |
var (name, universe, canFly) = heroes[0]; | |
System.Console.WriteLine(name); | |
record Hero(string Name, string Universe, bool CanFly = false); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var heroes = new Hero[] { | |
new("Homelander", "DC", true), | |
new("Groot", "Marvel", false), | |
new RealLifeHero("Superman", "DC", true, "Clark", "Kent"), | |
// Next object uses alternative constructor | |
new RealLifeHero("Jessica", "Jones", "Marvel", false), | |
}; | |
interface IHero | |
{ | |
string Name { get; } | |
string Universe { get; } | |
} | |
// Records can implement interfaces | |
record Hero(string Name, string Universe, bool CanFly) : IHero | |
{ | |
// Records can have methods just like regular classes | |
public override string ToString() => | |
$"{Name} ({Universe})"; | |
} | |
// Records can have base records | |
record RealLifeHero(string Name, string Universe, bool CanFly, | |
string RealLifeFirstName, string RealListLastName) | |
: Hero(Name, Universe, CanFly) | |
{ | |
// Records can have additional members | |
public string FullName => $"{RealLifeFirstName} {RealListLastName}"; | |
// Records can have additional constructors that call the | |
// constructor generated for the record. | |
public RealLifeHero(string firstName, string lastName, | |
string universe, bool canFly) : this($"{firstName} {lastName}", | |
universe, canFly, firstName, lastName) | |
{ } | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Immutable; | |
Hero homelander; | |
var heroes = new Hero[] { | |
homelander = new("Homelander", "DC", true), | |
// Create copy of homelander with changed name | |
homelander with { Name = "Stormfont" }, | |
new("Groot", "Marvel", false), | |
}; | |
// Fix typo in hero at index 1. We need to create | |
// a copy of the object to achieve that. | |
heroes[1] = heroes[1] with { Name = "Stormfront" }; | |
// Records work very well in conjunction with immutable collections. | |
var immutableArray = ImmutableArray<Hero>.Empty; | |
immutableArray = immutableArray.Add(homelander = new("Homelander", "DC", true)); | |
immutableArray = immutableArray.Add(homelander with { Name = "Stormfont" }); | |
immutableArray = immutableArray.SetItem(1, immutableArray[1] with | |
{ Name = "Stormfront" }); | |
record Hero(string Name, string Universe, bool CanFly); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Hero homelander; | |
var heroes = new Hero[] { | |
// Assign value to init-only property | |
homelander = new("Homelander", "DC") { CanFly = true }, | |
homelander with { Name = "The Deep" }, | |
new("Groot", "Marvel"), | |
}; | |
// Change init-only property using `with` | |
heroes[1] = heroes[1] with { CanFly = false }; | |
record Hero(string Name, string Universe) | |
{ | |
public bool CanFly { get; init; } = false; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Text.Json; | |
using System.Text.Json.Serialization; | |
var heroes = new Hero[] { | |
new("Homelander", "DC", true), | |
new("Jessica Jones", "Marvel"), | |
}; | |
foreach (var h in heroes) | |
{ | |
var json = JsonSerializer.Serialize(h); | |
// Will lead to e.g. `{"hn":"Homelander","univ":"DC"}` | |
Console.WriteLine(json); | |
} | |
record Hero(string Name, string Universe, bool CanFly = false) | |
{ | |
[JsonPropertyName("hn")] | |
public string Name { get; init; } = Name; | |
[JsonPropertyName("univ")] | |
public string Universe { get; init; } = Universe; | |
[JsonIgnore] | |
public bool CanFly { get; init; } = CanFly; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using static System.Console; | |
var hero1 = new Hero("Homelander", "DC", true); | |
var hero2 = hero1; | |
var hero3 = new Hero("Homelander", "DC", true); | |
// Obviously, hero1 and hero2 are equal. | |
if (hero1 == hero2) WriteLine("Hero 1 and 2 are equal"); | |
// hero1 and hero3 are also equal, because properties | |
// are compared, not object references. | |
if (hero1 == hero3) WriteLine("Hero 1 and 3 are equal, too"); | |
var hero4 = new RealLifeHero("Superman", "DC", true) | |
{ RealLifeFirstName = "Clark", RealLifeLastName = "Kent" }; | |
var hero5 = new RealLifeHero("Superman", "DC", true) | |
{ RealLifeFirstName = "Foo", RealLifeLastName = "Bar" }; | |
// hero4 and hero5 are NOT equal, because values of additional | |
// init-only properties are complared, too. | |
if (hero4 == hero5) WriteLine("Hero 4 and 5 are equal, too"); | |
record Hero(string Name, string Universe, bool CanFly = false); | |
record RealLifeHero(string Name, string Universe, bool CanFly) | |
: Hero(Name, Universe, CanFly) | |
{ | |
// Note that RealLifeHero has two additional init-only properties. | |
// We want to explore whether they are compared, too. | |
public string RealLifeFirstName { get; init; } | |
public string RealLifeLastName { get; init; } | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Reference AutoMapper NuGet package for this sample | |
using AutoMapper; | |
using static System.Console; | |
var homelander = new Hero("Homelander", "DC", true); | |
var traditionalHomelander = new TraditionalHero | |
{ Name = "Homelander", Universe = "DC", CanFly = true }; | |
// Use AutoMapper to convert record into traditional | |
// class and vice versa. | |
var config = new MapperConfiguration(cfg => | |
{ | |
cfg.CreateMap<Hero, TraditionalHero>(); | |
cfg.CreateMap<TraditionalHero, Hero>(); | |
}); | |
var mapper = config.CreateMapper(); | |
// Record into class | |
var traditionalHero = mapper.Map<TraditionalHero>(homelander); | |
WriteLine(traditionalHero.Name); | |
// Class into record | |
var hero = mapper.Map<Hero>(traditionalHomelander); | |
WriteLine(hero.Name); | |
record Hero(string Name, string Universe, bool CanFly = false); | |
class TraditionalHero | |
{ | |
public string Name { get; set; } | |
public string Universe { get; set; } | |
public bool CanFly { get; set; } | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// You need Entity Framework Core 5.0 and | |
// SQL LocalDB for this sample. | |
using Microsoft.EntityFrameworkCore; | |
using Microsoft.EntityFrameworkCore.Design; | |
using Microsoft.Extensions.Configuration; | |
using System.Collections.Generic; | |
using System.Linq; | |
using static System.Console; | |
var factory = new HeroContextFactory(); | |
using var context = factory.CreateDbContext(null); | |
// Add a record to the database. | |
var hero = new Hero(0, "Homelander", "DC", true); | |
context.Heroes.Add(hero); | |
await context.SaveChangesAsync(); | |
// Working with relations works, too | |
var team = new Team(0, "The Seven") | |
{ Heroes = new() { hero, new Hero(0, "The Deep", "DC", false) } }; | |
context.Teams.Add(team); | |
await context.SaveChangesAsync(); | |
// Note that the ID will be set by EFCore, but NOT | |
// using the clone/`with` mechanism. The ID is changed | |
// in the existing object -> breaks immutability rule. | |
WriteLine($"The ID of the new row in the DB is {hero.ID}"); | |
// If we want to update an object, we have to detach | |
// it first because we must create a copy of the record | |
// and attach the copy instead of the original object. | |
context.Entry(hero).State = EntityState.Detached; | |
hero = hero with { Name = "Stormfront" }; | |
context.Heroes.Update(hero); | |
await context.SaveChangesAsync(); | |
// Querying with records works just fine. | |
var theSeven = await context.Teams | |
.Include(t => t.Heroes) | |
.FirstAsync(t => t.Name == "The Seven"); | |
WriteLine(string.Join(',', theSeven.Heroes.Select(h => h.ID))); | |
record Team(int ID, string Name) | |
{ | |
public List<Hero> Heroes { get; init; } = new(); | |
} | |
record Hero(int ID, string Name, string Universe, bool CanFly); | |
class HeroContext : DbContext | |
{ | |
public HeroContext(DbContextOptions<HeroContext> options) | |
: base(options) | |
{ } | |
// A record is just a special form of a class, so we | |
// can use it with Entity Framework. | |
public DbSet<Hero> Heroes { get; set; } | |
public DbSet<Team> Teams { get; set; } | |
} | |
class HeroContextFactory : IDesignTimeDbContextFactory<HeroContext> | |
{ | |
public HeroContext CreateDbContext(string[] args) | |
{ | |
var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); | |
var optionsBuilder = new DbContextOptionsBuilder<HeroContext>(); | |
optionsBuilder.UseSqlServer(configuration["ConnectionStrings:DefaultConnection"]); | |
return new HeroContext(optionsBuilder.Options); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.AspNetCore.Builder; | |
using Microsoft.AspNetCore.Hosting; | |
using Microsoft.AspNetCore.Mvc; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.Hosting; | |
using Microsoft.Extensions.Logging; | |
using System.Collections.Generic; | |
using System.Linq; | |
Host.CreateDefaultBuilder(args) | |
.ConfigureWebHostDefaults(webBuilder => | |
{ | |
webBuilder | |
.ConfigureServices(services => services.AddControllers()) | |
.Configure(app => | |
{ | |
app.UseRouting(); | |
app.UseEndpoints(endpoints => endpoints.MapControllers()); | |
}); | |
}) | |
.Build() | |
.Run(); | |
[Route("api/[controller]")] | |
[ApiController] | |
public class HeroesController : ControllerBase | |
{ | |
public record HeroDto(int ID, string Name, string Universe, bool CanFly); | |
private List<HeroDto> Heroes { get; } = new() | |
{ | |
new(1, "Homelander", "DC", true), | |
new(2, "Groot", "Marvel", false), | |
}; | |
[HttpGet] | |
[Route("")] | |
public IActionResult GetAllHeroes() => Ok(Heroes); | |
[HttpGet("{id}", Name = nameof(GetHeroById))] | |
public IActionResult GetHeroById(int id) => | |
Heroes.FirstOrDefault(h => h.ID == id) switch | |
{ | |
null => NotFound(), | |
var h => Ok(h) | |
}; | |
[HttpPost] | |
public IActionResult AddHero([FromBody] HeroDto hero) | |
{ | |
Heroes.Add(hero); | |
return CreatedAtRoute(nameof(GetHeroById), new { id = hero.ID }, hero); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment